Classpath not working when running Scala script

174 views Asked by At

I have a successfully compiled a Java class called bnc.Encryption in my lib directory: /Users/satchwinston/Development/test/lib.

I'm using Java 21 and Scala 3.3.1, I'm NOT using the Metals extension.

I execute my Scala script like this: ./encryptor which in turns runs scala -explain -classpath "/Users/satchwinston/Development/test/lib"

I get the following result:

-- [E006] Not Found Error: /Users/satchwinston/Development/test/scala-cli/src/main/scala/./encryptor:7:7 
7 |import bnc.Encryption
  |       ^^^
  |       Not found: bnc
  |-----------------------------------------------------------------------------
  | Explanation (enabled by `-explain`)
  |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  | The identifier for `bnc` is not bound, that is,
  | no declaration for this identifier can be found.
  | That can happen, for example, if `bnc` or its declaration has either been
  | misspelt or if an import is missing.
   -----------------------------------------------------------------------------
1 error found
Errors encountered during compilation

Here is my encryptor Scala script:

#!/usr/bin/env scala -explain -classpath "/Users/satchwinston/Development/test/lib"
/* $Id$ */

import java.nio.file.Files
import java.nio.file.Path

import bnc.Encryption

val usage = """
  Usage: encryptor ([-e] | [-d]) ([-in filename] | <arg>) > <filename>
"""

def encrypt(string: String): String =
  Encryption.encrypt(string)

def decrypt(string: String): String =
  Encryption.decrypt(string)

@main def encryptor(args: String*): Unit =
  if (args.isEmpty || args.length != 2 || args.length != 3) {
    println(usage)
    sys.exit(1)
  }

  val e: Boolean = args(0) == "-e"
  val d: Boolean = args(0) == "-d"
  val in: String = if (args(1) == "-in") args(2) else ""
  val str: String = if (args(1) != "-in") args(1) else ""

  if (e && str.length() > 0)
    println(encrypt(str))
  else if (e)
    println(encrypt(Files.readString(Path.of(in))))
  else if (d && str.length > 0)
    println(decrypt(str))
  else if (d)
    println(decrypt(Files.readString(Path.of(in))))
  else {
    println(usage)
    sys.exit(1)
  }

  sys.exit(0)
2

There are 2 answers

0
Mateusz Kubuszok On

I was able to make your script work like this:

// bnc/Encryption.java - stub since I do not know your implementation
package bnc;

public class Encryption {

  public static String encrypt(String s) { return s; }
  public static String decrypt(String s) { return s; }
}
#!/usr/bin/env -S scala-cli shebang --server=false
//> using scala 3.3.1
//> using option -explain
//> using jvm zulu-jre:21.0.0
//> using file bnc/Encryption.java

import java.nio.file.Files
import java.nio.file.Path

import bnc.Encryption

val usage = """
  Usage: encryptor ([-e] | [-d]) ([-in filename] | <arg>) > <filename>
"""

def encrypt(string: String): String =
  Encryption.encrypt(string)

def decrypt(string: String): String =
  Encryption.decrypt(string)

def encryptor(args: Array[String]): Unit =
  if (args.isEmpty || args.length != 2 || args.length != 3) {
    println(usage)
    sys.exit(1)
  }

  val e: Boolean = args(0) == "-e"
  val d: Boolean = args(0) == "-d"
  val in: String = if (args(1) == "-in") args(2) else ""
  val str: String = if (args(1) != "-in") args(1) else ""

  if (e && str.length() > 0)
    println(encrypt(str))
  else if (e)
    println(encrypt(Files.readString(Path.of(in))))
  else if (d && str.length > 0)
    println(decrypt(str))
  else if (d)
    println(decrypt(Files.readString(Path.of(in))))
  else {
    println(usage)
    sys.exit(1)
  }

  sys.exit(0)

encryptor(args)

Addressing what you wrote:

  1. if you have Scala CLI you do NOT NEED another Scala installation. It is an all in one package: scala + scalac + build tool, the replacement for several older tools.
  2. Scala version can be changed both by a flag as well as a directive
  3. You need to use .scala extension if you are building a "program" rather than a "script" - programs have entrypoints which tells JVM where to start (@main), while scripts evaluate everything from top to the bottom within a file you are executing (so they do not need any main). Remove @main, and call your method yourself with args and you return to "script" mode
    • in case anyone is interested why @main exists if Scala CLI is for scripts: it is not only for scripts. Create several .scala files in a directory, in 1 of them (preferably project.scala) put using directives with Scala version, JVM version, platform, dependencies, etc. Then scala-cli compile . will build a normal project, which would need an entrypoint if you did scala-cli run .. These Scala files do need the .scala extension.
    • however, single Scala files evaluated from the top to the bottom, not intended to be distributed as JARs nor opened in IDEs, (optionally referring other .scala or .java files), can be written as scripts, where their whole body is a main of sort. These files use .sc extension, although with a shebang you can also use no extension at all
  4. JVM version can be changed both by a flag as well as a directive. Scala CLI uses coursier and its coursier java --available allows printing all versions that can be used there. Bloop makes sure that you are using a server to keep JVM warm between consecutive calls. You can disable it with a --server=false flag.
  5. and 6. - your personal stylistic preferences are a non-technical issue. Also: you don't need to use -classpath or --classpath - there is an --extra-jars flag for adding JARs (or .classes) to the runtime, as well as //> using jar///> using jars directive for adding a JAR. Because a situation where you have a standalone .class which is not a part of JAR but also there is no source (which you could add with //> using file filename.java) is rather rare.
10
Donald Winston On

Apparently the scala-cli way of doing things is not up to speed yet:

  1. I had to install scala-cli separately from my Scala installation.
  2. The scala-cli is only at version 3.3.0. My scala installation is at 3.3.1.
  3. I have to use the .scala extension in the scripts filename.
  4. The JVM in the ridiculous "bloop" server is using Java 17. My Java installation is using Java 21. Can't figure out how to change JVM. Why the heck do I need a "bloop" server?
  5. I have to use this ridiculous thing instead of a normal shebang: #!/usr/bin/env -S scala-cli shebang -S 3
  6. It looks like you got to use --classpath not -classpath. No --cp? Get real.

Hmmmm. Maybe Groovy is a better option.