Running scala 3 scripts

I am trying to use Scala 3 as scripting language now, encountered few obstacles, so i wonder what is the right way to do it.
I am using the latest “Scala code runner version 3.1.2 – Copyright 2002-2022, LAMP/EPFL”.

First thing I did, wrote a one line script Hello.scala and executed it:

scala Hello.scala

It was too slow, a few seconds, so I realized it compiles it every time. Then I found somebody’s suggestion to use ‘-savecompiled’ flag:

scala -savecompiled Hello.scala

This actually created Hello.jar file in the same directory, but the run was still slow. I realized that it does not use the Hello.jar file for the run. So then I tried this:

java -jar Hello.jar

This worked and it was fast. But do I really need to do such workaround? By the way, when I ran ‘scala -help’, I did not see ‘-savecompiled’ in the options list, so it looks like undocumented feature. What other features are there undocumented?

Here is my first question: maybe there is an option to tell scala to use the saved jar file if it exists and up to date?

Next thing I tried: reusing code defined in another scala file. Here are my two files:

Util.scala

object Util:
    def utiltest() = println("This is utiltest")

Hello.scala

import Util._

@main def hello =
    println("Hello, world")
    utiltest()

The ‘scala Hello.scala’ did not work, and ‘scala -cp . Hello.scala’ did not work either. The only way to make it work was to use scalac first to compile both files (or at least Util.scala) and then use ‘-cp’ when running it.
The good news was, if I also use ‘-savecompiled’, it created Hello.jar that contained everything that is needed to run the program.

Here is my second question: is there any way to tell scala to search for dependencies, even if they are source files (this is the whole point of scripting)?

1 Like

I don’t know if you know about scala-cli but it solves the problems you have highlighted here.
It saves the compiled output automatically and keeps a compiler process in the background to make subsequent compilation faster.
It can also compile multiple files at once, you just need to pass a directory and it will compile and run them all together.

2 Likes

@lolgab Thanks, I just tried it. Downloaded version 0.1.5 - I guess, it is the latest.
Two major issues. First, it tried to download something from internet (Bloop), but we are behind corporate firewall and all artifact downloads are handled through our Nexus server. So it failed.
Second, I am not sure it supports Scala 3. At least the Bloop that it tried to download uses 2.12.

I need this for Scala 3.

If you write the magic comment
//> using scala "3"
in the beginning of your code file scala-cli will download and use latest scala 3 for you. See more here:

Years ago I tried building a Scala scripting environment, but it was a lot of work, the Scala ecosystem was not that far along yet… Because of you, I started reading up on scala-cli, and it looks really exciting… I will try it out soon. Also, thank you @bjornregnell for the Scala 3 advice…

1 Like

The scala “3” comment did not hep in my case, it still tries to download Bloop for 2.12:

scala.build.errors.FetchingDependenciesError: Error downloading io.github.alexarchambault.bleep:bloop-frontend_2.12:1.4.20

I think I will try Ammonite , they now have a version supporting Scala 3. I previously used their mill building tool, which is based on Ammonite scripting.

It’s normal for various Scala tools such as sbt and scala-cli to use a Scala version (such as 2.12) internally, even if your code doesn’t use that Scala version.

Perhaps Ammonite will work for you, so feel free to go down that route, but you might also consider configuring your environment to allow 2.12 to be downloaded. The fact that 2.12 is needed doesn’t indicate that anything has malfunctioned.

Thanks @SethTisue
The actual problem is still the attempt to download; as I mentioned before, we are behind the firewall.

The Ammonite I tried today, for some reason it was not good either. It did not recognize the ‘@main’ annotation, while it says in its own docs that it should work. I mean, it did not complain about ‘@main’, but it did not invoke the function either. Only executed the code at the top level of the script.

Anyway, I went back to the regular scala runner that comes with scala 3.1.2 package (the one I started with), and decided to write a lightweight shell wrapper.
Below is the script, if anybody is interested. It checks all possible scala source files under the top directory, and recompiles if necessary. Otherwise it uses the jar directly.
The first argument to the script must be the main source file, the rest is the list of the arguments for the program itself which will be passed to scala main method.

#!/bin/bash

file=$(readlink -f "$1")
shift
dir=$(dirname "$file")
cd $dir

mainfile=$(basename "$file")
filename="${mainfile%.*}"
jarfile="${filename}.jar"

mkdir -p classes

sources=$(find . \( -name "*.sc" -o -name "*.scala" \) )

# Check if jar file exists and is newer than any source
# If not, need to recompile

compile=yes

if [ -f "$jarfile" ]
then
    compile=
    for f in $sources
    do
        if [ $f -nt $jarfile ]
        then
            echo $f is newer than $jarfile
            compile=yes
            break
        fi
    done
fi

if [ "$compile" ]
then
    echo Compile $sources
    scalac -d classes -cp classes $sources
    scala -savecompiled -cp classes $mainfile $@
else
    echo Execute using jarfile $jarfile
    java -jar $jarfile $@
fi
1 Like

Thanks for the swanky script.

It’s painful to see anyone suffer for “how do I run a script?” A lot of work has gone into this simple [sic] question, with a few somewhat divergent solutions. I don’t think there has to be only one canonical solution, but from a menu of a few answers, there should always be one answer that just works.

Especially in whatever year this is.

I’m aware that Scala 3 REPL has seen some improvements, and scala-cli and Coursier cs are much-beloved, as well as Ammonite.

It may be that certain use cases are challenging (I was once stuck behind a firewall), but it would be nice if we could say which solution handles that best. (Maybe some tool does interrupted retries with more aplomb.)

Thanks for the opportunity to say “aplomb”.

1 Like

Can you can use a proxy to download stuff? If you can, then use JAVA_OPTS. Here is an example:

export JAVA_OPTS="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -Dhttps.proxyHost=proxy -Dhttps.proxyPort=8080"

Mill scripts can use these proxies. Note that Mill uses Ammonite under the hood, and it uses the OS environment variable. Ammonite also allows you to access the proxies in the script using ammonite.main.ProxyFromEnv.setPropProxyFromEnv() call - in case you need it in your script.

If you need to use your local Maven artifact server, Mill also allows you to point to your own repository. Here is an example:

import coursier.maven.MavenRepository

object CustomZincWorkerModule extends ZincWorkerModule with CoursierModule {
  def repositoriesTask() = T.task { super.repositoriesTask() ++ Seq(
    MavenRepository("https://oss.sonatype.org/content/repositories/releases")
  ) }
}

object YourBuild extends ScalaModule {
  def zincWorker = CustomZincWorkerModule
  // ... rest of your build definitions
}

Note that Mill uses Scala 2.12 for the script itself, but you set-up the module to use Scala 3.

A final note. The Mill script (which weighs in at under 150KB), will download the Mill artifact per se on its first execution. But you can also download it manually and place it in the Mill cache if using the proxy is not possible.

Having said all this, I suspect you will quickly find it difficult to develop all but the simplest projects using Bash scripts. I suggest Mill or SBT. SBT also lets you configure what I described above.

HTHs

Thanks everybody for suggestions.
I am still proceeding with using my script. The reason is, I wanted a simple solution that would work in production environment.
The other ideas are interesting. I actually have a mix of gradle and mill projects, but those have to be built and deployed (too rigid); I wanted scala in a script fashion, so I can use it instead of python.

I still believe there is a problem with scala runner in 3.1.2 version. According to scala man page , when I use ‘-savecompiled’, it should automatically use the jar file on the second run. Looks like it used to work in 2.10 version. And again, the ‘-savecompiled’ in 3.1.2 is not shown on help. So it looks like a bug.

Where can I report it?

You do realize that Ammonite/Mill are scripting engines, right? Instead of “jar -cp xpto.jar className” you just “mill -i script.sc command”. No need to build and find jars.

HTHs

Thanks @hmf . I opened an issue here: Failure to use jar file produced by 'scala -savecompiled' · Issue #15180 · lampepfl/dotty · GitHub .

And yes, I understand that Ammonite/Mill are scripting engines, but they still will want to download something on the fly, which would not work in production environment.

I found the problem and closed the issue.
The ‘-savecompiled’ option is not working as expected, but ‘-save’ does.