I am trying to write a simple console client application where i can present some options to the user, get their input and act accordingly. If i run the code through intellij or paste it into the scala console, it works. If i run it through sbt (which is how i really need it to run), i run into all sorts of problems.
I have sbt version 0.13.8, OS is Mac, my build.sbt contains:
scalaVersion := "2.11.6"
fork in run := true
EDIT I started with the minimum scala activator template in case that is useful info in this context
I have simplified the code to barebones,
import scala.io.StdIn._
object TestClient {
def main(args: Array[String]): Unit = {
join()
}
def join(): Unit = {
val name = readLine(s"Enter your name.${System.getProperty("line.separator")}")
name match {
case n: String => println(n)
case o => {
println(s"invalid name ${o}")
join()
};
}
}
}
When go into sbt and from the prompt enter run. one of the following seems to happen
1) I get this exception as soon as i run
Exception in thread "Thread-2" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:569)
at java.lang.StringBuffer.append(StringBuffer.java:369)
at java.io.BufferedReader.readLine(BufferedReader.java:370)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at sbt.BasicIO$$anonfun$processFully$1$$anonfun$apply$8.apply(ProcessImpl.scala:58)
at sbt.BasicIO$$anonfun$processFully$1$$anonfun$apply$8.apply(ProcessImpl.scala:58)
at sbt.BasicIO$.readFully$1(ProcessImpl.scala:63)
at sbt.BasicIO$.processLinesFully(ProcessImpl.scala:69)
at sbt.BasicIO$$anonfun$processFully$1.apply(ProcessImpl.scala:58)
at sbt.BasicIO$$anonfun$processFully$1.apply(ProcessImpl.scala:55)
at sbt.SimpleProcessBuilder$$anonfun$3.apply$mcV$sp(ProcessImpl.scala:354)
at sbt.Spawn$$anon$3.run(ProcessImpl.scala:17)
2)
No memory issues, but getting these messages in an infinite loop, so the readline does not seem to be waiting for any input
background log: info: Enter your name.
background log: info: invalid name null
background log: info: Enter your name.
background log: info: invalid name null
...
Either way I am unable to actually enter any input in the console. Not sure what I'm missing or doing wrong
Put
connectInput in run := true
in your build.sbt. See the official docs for more info on how to correctly handle forks in sbt.
Related
In the book, Programming in Scala 5th Edition, the author says the following for two classes:
Neither ChecksumAccumulator.scala nor Summer.scala are scripts, because they end in a definition. A script, by contrast, must end in a result expression.
The ChecksumAccumulator.scala is as follows:
import scala.collection.mutable
class CheckSumAccumulator:
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = ~(sum & 0XFF) + 1
object CheckSumAccumulator:
private val cache = mutable.Map.empty[String, Int]
def calculate(s: String): Int =
if cache.contains(s) then
cache(s)
else
val acc = new CheckSumAccumulator
for c<-s do
acc.add((c >> 8).toByte)
acc.add(c.toByte)
val cs = acc.checksum()
cache += (s -> cs)
cs
whereas the Summer.scala is as follows:
import CheckSumAccumulator.calculate
object Summer:
def main(args: Array[String]): Unit =
for arg <- args do
println(arg + ": " + calculate(arg))
But when I run the Summer.scala file, I get a different error than what mentioned by the author:
➜ learning-scala git:(main) ./scala3-3.0.0-RC3/bin/scala Summer.scala
-- [E006] Not Found Error: /Users/avirals/dev/learning-scala/Summer.scala:1:7 --
1 |import CheckSumAccumulator.calculate
| ^^^^^^^^^^^^^^^^^^^
| Not found: CheckSumAccumulator
longer explanation available when compiling with `-explain`
1 error found
Error: Errors encountered during compilation
➜ learning-scala git:(main)
The author mentioned that the error would be around not having a result expression.
I also tried to compile CheckSumAccumulator only and then run Summer.scala as a script without compiling it:
➜ learning-scala git:(main) ./scala3-3.0.0-RC3/bin/scalac CheckSumAccumulator.scala
➜ learning-scala git:(main) ✗ ./scala3-3.0.0-RC3/bin/scala Summer.scala
<No output, given no input>
➜ learning-scala git:(main) ✗ ./scala3-3.0.0-RC3/bin/scala Summer.scala Summer of love
Summer: -121
of: -213
love: -182
It works.
Obviously, when I compile both, and then run Summer.scala, it works as expected. However, the differentiation of Summer.scala as a script vs normal file is unclear to me.
Let's start top-down...
The most regular way to compile Scala is to use a build tool like SBT/Maven/Mill/Gradle/etc. This build tool will help with a few things: downloading dependencies/libraries, downloading Scala compiler (optional), setting up CLASS_PATH and most importantly running scalac compiler and passing all flags to it. Additionally it can package compiled class files into JARs and other formats and do much more. Most relevant part is CP and compilation flags.
If you strip off the build tool you can compile your project by manually invoking scalac with all required arguments and making sure your working directory matches package structure, i.e. you are in the right directory. This can be tedious because you need to download all libraries manually and make sure they are on the class path.
So far build tool and manual compiler invocation are very similar to what you can also do in Java.
If you want to have an ah-hoc way of running some Scala code there are 2 options. scala let's you run scripts or REPL by simply compiling your uncompiled code before it executes it.
However, there are some caveats. Essentially REPL and shell scripts are the same - Scala wraps your code in some anonymous object and then runs it. This way you can write any expression without having to follow convention of using main function or App trait (which provides main). It will compile the script you are trying to run but will have no idea about imported classes. You can either compile them beforehand or make a large script that contains all code. Of course if it starts getting too large it's time to make a proper project.
So in a sense there is no such thing as script vs normal file because they both contain Scala code. The file you are running with scala is a script if it's an uncompiled code XXX.scala and "normal" compiled class XXX.class otherwise. If you ignore object wrapping I've mentioned above the rest is the same just different steps to compile and run them.
Here is the traditional 2.xxx scala runner code snippet with all possible options:
def runTarget(): Option[Throwable] = howToRun match {
case AsObject =>
ObjectRunner.runAndCatch(settings.classpathURLs, thingToRun, command.arguments)
case AsScript if isE =>
ScriptRunner(settings).runScriptText(combinedCode, thingToRun +: command.arguments)
case AsScript =>
ScriptRunner(settings).runScript(thingToRun, command.arguments)
case AsJar =>
JarRunner.runJar(settings, thingToRun, command.arguments)
case Error =>
None
case _ =>
// We start the repl when no arguments are given.
if (settings.Wconf.isDefault && settings.lint.isDefault) {
// If user is agnostic about -Wconf and -Xlint, enable -deprecation and -feature
settings.deprecation.value = true
settings.feature.value = true
}
val config = ShellConfig(settings)
new ILoop(config).run(settings)
None
}
This is what's getting invoked when you run scala.
In Dotty/Scala3 the idea is similar but split into multiple classes and classpath logic might be different: REPL, Script runner. Script runner invokes repl.
In my SBT (0.13.16) build, I have the following task:
startThing := {
var bin_path = s"${file(".").getAbsolutePath}/bin"
val result = s"$bin_path/start_thing".!
if (result != 0)
throw new RuntimeException("Could not start Thing..")
true
}
And start_thing contains:
(run_subprocess &)
and my build hangs.
I can see that start_thing exits (the process table does not have it as an entry) but adding some printlns to the task shows that it's stuck on val result = s"$bin_path/start_thing".!.
If I kill the run_subprocess process then SBT unblocks and runs normally.
In this particular case, run_subprocess has set up some Kubernetes port-forwarding that needs to be there in order for subsequent tests to work.
Try daemonising the background process like so
(run_subprocess >/dev/null 2>&1 &)
The issue could be output from run_subprocess still going to sbt parent as suggested here.
I was able to replicate the issue in both sbt 0.13.17 and 1.0.2. Daemonising worked in both.
Regardless of my comment, in my case the reason for the hanging was ACTUALLY the potential leaving of open STDOUT , STDERR handles in a daemon started by the script , that is OK:
/usr/local/bin/minio server "$minio_data_dir" > /dev/null 2>&1 & # and start the server
and NOT ok:
/usr/local/bin/minio server "$minio_data_dir" 2>&1 & # and start the server
So the hanging occurred randomly EVEN with the accepted answers start in the background ...
Thus this solution needed NOT, any wrapper bash scripts ... This is how the code looked in the build.sbt
lazy val startLocalS3 = inputKey[Unit]("localS3")
lazy val startLocalS3Task = TaskKey[Unit]("localS3", "create local s3")
lazy val core: Project = project
.in(file("."))
.settings(
name := "rfco-core",
startLocalS3Task := {
val cmd: Seq[String] = Seq("bash" , "-c" , "./CI/start-s3-svr.sh")
import sys.process._
cmd.mkString(" ").!!
},
fork in startLocalS3Task := true,
compile.in(Compile) := (compile in Compile).dependsOn((startLocalS3Task)).value
// you might want to use Test scope ^^ here
)
I have the following scala program that simply counts the words in a file:
package com.impatient
import java.util.Scanner
object Main extends App {
countWords()
def countWords(): Unit = {
val in = new Scanner(new java.io.File("C:\\tmp\\SampleText.txt"))
var wc = 0
while (in.hasNext()) {
var word = in.next()
wc += 1
println( wc + ". " + word)
}
in.close()
println("======================")
println(s"Total words: $wc")
}
def TestFunction(): Unit ={
println("Hello from test function!")
}
TestFunction()
}
When I execute this in intellij, either directly or via the sbt shell inside intellij, I get the following output:
mostly
positive:
======================
Total words: 309
Hello from test function!
So it correctly counts 309 words in the text file.
However if I try to execute the jar that I just compiled with intellij, directly, either via scala or sbt I get the following output:
λ scala .\impatientscala2_2.12-1.0.jar
======================
Total words: 0
Hello from test function!
So in this case, although the methods in Main are executed the wordcount is 0.
Why would be this the case? Is intellij referencing some other java libraries that standalone sbt or scala isn't? In that case why doesn't the Scanner fail?
Edit 1
I have cloned this repo to another PC to see what happens. And in that case I can observe the exact opposite, i.e.:
The program returns 0 word count when run from Intellij
When the jar is run directly with scala, it gives the current wordcount.
The 0 wordcount is due to not processing the while loop, as in.hasNext() returns nothing.
I found a workaround by using Paths.get from java.nio.file._.
//val in = new Scanner(new java.io.File("C:/tmp/SampleText.txt"))
val in: Scanner = new Scanner(Paths.get("C/tmp/SampleText.txt"))
This produces the correct jars on both PCs, when using Run | Sbt Task in Intellij, using SBT Shell in intellij, and using standalone SBT shell. If anyone can provide an explanation of why would java.io.File fail in certain setups that would be great.
I have a subproject in my build.sbt with a rather long setting for initialCommands, comprising a list of imports and some definitions. I'd like to test this as part of regular CI, because otherwise I won't notice breaking changes after refactoring code. It is not clear to me how to do so.
Just running sbt console doesn't seem to cut it, because there is always a "successful" exit code even when code doesn't compile.
Moving the code out into an object defined in a special source file won't help because I need the list of imports to be present (and I don't want to cakeify my whole code base).
Moving the code out into a source file and then loading that with :load also always gives a successful exit code.
I found out about scala -e but that does strange things on my machine (see the error log below).
This is Scala 2.12.
$ scala -e '1'
cat: /release: No such file or directory
Exception in thread "main" java.net.UnknownHostException: <my-host-name-here>: <my-host-name-here>: Name or service not known
You could generate a file and run it like any other test file:
(sourceGenerators in Test) += Def.task {
val contents = """object TestRepl {
{{}}
}""".replace("{{}}", (initialCommands in console).value)
val file = (sourceManaged in Test).value / "repltest.scala"
IO.write(file, contents)
Seq(file)
}.taskValue
I have a very simple scala program:
object TakeInputs {
def main(args: Array[String]) {
val name = readLine("What is your name?")
println(name)
}
}
When I try to run this with
sbt "project myproject" "run-main TakeInput"
it doesn't wait for user input and the program just finishes with
What is your name?null
as the output.
Is there a way to make sbt wait for user input (like what happens if "readLine" is run in sbt console)? I can provide the inputs as command line parameters but I have a lot of them and I would like to make the program more user-friendly by displaying messages indicating what the user should enter next. Thanks.
Add the following to your build.sbt
connectInput in run := true
From the sbt documentation in Configuring Input