sbt: execute initialCommands silently - scala

Is it possible to execute initialCommands in the console task silently, i.e. as if
:silent
val $session = new foo.bar.Session()
import $session._
import $session.lib._
:silent
Putting these commands in initialCommands doesn't work, though, because :<command> commands apparently cannot be used in initialCommands:
Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_102).
Type in expressions for evaluation. Or try :help.
scala> <console>:2: error: illegal start of definition
:silent
^
Interpreter encountered errors during initialization!
[error] (Thread-1) java.lang.InterruptedException
java.lang.InterruptedException
at java.util.concurrent.SynchronousQueue.put(SynchronousQueue.java:879)
at scala.tools.nsc.interpreter.SplashLoop.run(InteractiveReader.scala:77)
at java.lang.Thread.run(Thread.java:745)

Unfortunately, as of 0.13.13, sbt runs the initialCommands early, while it's creating the interpreter, and before the console has a chance to bind the interpreter as $intp.
This is close:
$ sbt -Dscala.repl.maxprintstring=-1
[info] Set current project to sbt-test (in build file:/home/apm/tmp/sbt-test/)
> console
[info] Starting scala interpreter...
[info]
Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.
scala> ...
scala> Future(42)
...
scala> $intp.isettings.max
maxAutoprintCompletion maxPrintString
scala> $intp.isettings.maxPrintString = 1000
$intp.isettings.maxPrintString: Int = 1000
scala> "hi"*1000
res0: String = hihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihihi...
scala> Future(42)
res1: scala.concurrent.Future[Int] = Future(Success(42))
It's a misfeature that setting maxPrintString to zero doesn't truncate everything, including the ellipsis, which is always residual.

I'm unaware of an sbt option to do that. In the lack of a better solution, you could hide all your setup in nice looking import as follows:
object console {
object setup {
val bar = foo.bar
bar.init()
}
}
Edit 1:
Note that this is equivalent to the code original code you wrote: it put a thing in scope called bar, which points to foo.bar. You can also use the same technique with types to group whatever imports you need into a single one. This is the mechanism used Predef to magically get scala.collection.immutable.Set (both the type and the value) in scope.
Edit 2:
I guess your technique can't achieve that with a single import.
It still works. Suppose Session is defined as follows:
trait Session {
val v
def f
lazy val l
object o {}
type T
}
then
val $session = new foo.bar.Session()
import $session._
becomes
object console {
object setup {
val $session = new foo.bar.Session()
val v = $session.v
def f = $session.f
lazy val l = $session.l
val o = $session.o
type T = $session.T
}
}
You can apply this transformation recursively for lib._ and whatever other imports you have until you've built the exact same scope.

Related

initialCommands in consoleProject is not executing one or more imports

Given these two inconspicuous lines of code…
import scala.sys.process._
def less(s: String) = ("code -" #< new java.io.ByteArrayInputStream(s.getBytes)).!!
…defined like this…
lazy val consoleSupportSettings: Seq[Setting[_]] = Seq(
initialCommands in consoleProject := """import scala.sys.process._
|def less(s: String) = ("code -" #< new java.io.ByteArrayInputStream(s.getBytes)).!!""".stripMargin
)
…which eventually is added to the root project…
lazy val root =
Project(id = "root", base = file("."))
.settings(consoleSupportSettings)
scala seems to not really(*) execute the line import scala.sys.process._: When I fire up sbt and hop into consoleProject I am greeted by…
> consoleProject
[info] Starting scala interpreter...
[info]
<console>:19: error: value #< is not a member of String
def less(s: String) = ("code -" #< new java.io.ByteArrayInputStream(s.getBytes)).!!
^
[success] Total time: 1 s, completed Apr 29, 2021 10:58:09 AM
success indeed. NOT.
However, if I remove the setting…
> set initialCommands in consoleProject := ""
[info] Defining root/*:consoleProject::initialCommands
[info] The new value will be used by root/*:consoleProject
[info] Reapplying settings...
…
> consoleProject
…and then manually enter above code it works:
…
Welcome to Scala version 2.10.4 (OpenJDK 64-Bit Server VM, Java 1.8.0_272).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import scala.sys.process._
import scala.sys.process._
scala> def less(s: String) = ("code -" #< new java.io.ByteArrayInputStream(s.getBytes)).!!
less: (s: String)String
Why is that and how do I fix this?
Worthy of note is that the sbt version is 0.13.8, so not exactly hot off the press.
(*) fwiw I replaced import scala.sys.process._ with import scala.sys.processasdf._ and it, as expected, would complain that processasdf was not a member of scala.sys, so it's not as if the import was actually ignored.
Whilst I still don't know why, it turns out that having the import inside the def rather than globally does the job:
initialCommands in consoleProject := """
|def less(s: String) = {
| import scala.sys.process._
| ("code -" #< new java.io.ByteArrayInputStream(s.getBytes)).!!
|}
|""".stripMargin

How to create a Scala presentation compiler inside Ammonite REPL?

I want to create a Scala presentation compiler in Ammonite REPL, however I always got the error of Missing dependency 'object scala in compiler mirror'.
I have tried the workaround mentioned in object scala in compiler mirror not found - running Scala compiler programatically . Unfortunately it does not work.
How to make it work?
Welcome to the Ammonite Repl 1.0.0
(Scala 2.12.2 Java 1.8.0_131)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
# import scala.tools.nsc.Settings
import scala.tools.nsc.Settings
# import scala.tools.nsc.interactive.Global
import scala.tools.nsc.interactive.Global
# import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.nsc.reporters.ConsoleReporter
# val settings = new Settings()
settings: Settings = Settings {
-d = .
}
# settings.usejavacp.value = true
# val reporter = new ConsoleReporter(settings)
reporter: ConsoleReporter = scala.tools.nsc.reporters.ConsoleReporter#4a24170b
# val compiler = new Global(settings, reporter)
error: error while loading Object, Missing dependency 'object scala in compiler mirror', required by /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar(java/lang/Object.class)
scala.reflect.internal.MissingRequirementError: object scala in compiler mirror not found.
scala.reflect.internal.MissingRequirementError$.signal(MissingRequirementError.scala:17)
scala.reflect.internal.MissingRequirementError$.notFound(MissingRequirementError.scala:18)
scala.reflect.internal.Mirrors$RootsBase.$anonfun$getModuleOrClass$4(Mirrors.scala:54)
scala.reflect.internal.Mirrors$RootsBase.getModuleOrClass(Mirrors.scala:54)
scala.reflect.internal.Mirrors$RootsBase.getModuleOrClass(Mirrors.scala:66)
scala.reflect.internal.Mirrors$RootsBase.getPackage(Mirrors.scala:172)
scala.reflect.internal.Definitions$DefinitionsClass.ScalaPackage$lzycompute(Definitions.scala:169)
scala.reflect.internal.Definitions$DefinitionsClass.ScalaPackage(Definitions.scala:169)
scala.reflect.internal.Definitions$DefinitionsClass.ScalaPackageClass$lzycompute(Definitions.scala:170)
scala.reflect.internal.Definitions$DefinitionsClass.ScalaPackageClass(Definitions.scala:170)
scala.reflect.internal.Definitions$DefinitionsClass.init(Definitions.scala:1447)
scala.tools.nsc.Global$Run.<init>(Global.scala:1149)
scala.tools.nsc.interactive.Global$TyperRun.<init>(Global.scala:1308)
scala.tools.nsc.interactive.Global.newTyperRun(Global.scala:1331)
scala.tools.nsc.interactive.Global.<init>(Global.scala:286)
ammonite.$sess.cmd6$.<init>(cmd6.sc:1)
ammonite.$sess.cmd6$.<clinit>(cmd6.sc)
The same code works in official Scala REPL.
Welcome to Scala 2.12.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_131).
Type in expressions for evaluation. Or try :help.
scala> import scala.tools.nsc.Settings
import scala.tools.nsc.Settings
scala> import scala.tools.nsc.interactive.Global
import scala.tools.nsc.interactive.Global
scala> import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.nsc.reporters.ConsoleReporter
scala> val settings = new Settings()
settings: scala.tools.nsc.Settings =
Settings {
-d = .
}
scala> settings.usejavacp.value = true
settings.usejavacp.value: Boolean = true
scala> val reporter = new ConsoleReporter(settings)
reporter: scala.tools.nsc.reporters.ConsoleReporter = scala.tools.nsc.reporters.ConsoleReporter#7eeb38b2
scala> val compiler = new Global(settings, reporter)
compiler: scala.tools.nsc.interactive.Global = scala.tools.nsc.interactive.Global#3b6a4b91

Setting configuration properties to access in REPL

Given:
src/test/scala/net/Main.scala
package net
import com.typesafe.config.ConfigFactory
object Main extends App {
override def main(args: Array[String]) {
val bar = ConfigFactory.load().getString("app.bar")
val bippy = ConfigFactory.load().getString("app.bippy")
println(s"bar: $bar | bippy : $bippy")
}
}
src/test/resources/application.conf
app {
bar = ${?BAR}
bippy = ${?BIPPY}
}
I attempted to set the BAR and BIPPY environment variables in sbt:
>set envVars := Map("BAR" -> "bar!", "BIPPY" -> "bippy!")
Then, I opened the REPL in test mode:
>test:console
scala> import net.Main
import net.Main
scala> Main.main(Array())
com.typesafe.config.ConfigException$Missing: No configuration setting
found for key 'app.bar'
How can I set these properties for the REPL?
Pass your configuration file using the -Dconfig.file system property
[localhost]$ sbt -Dconfig.file=src/test/resources/application.conf
[info] Loading global plugins from ~/.sbt/0.13/plugins
[info] Loading project definition from ~/my/project
[info] Set current project to my-project (in build file:~/my/project/)
> console
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.11.6 (OpenJDK 64-Bit Server VM, Java 1.8.0_72-internal).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import com.typesafe.config._
import com.typesafe.config._
scala> val config = ConfigFactory.load()
config: com.typesafe.config.Config = Config(SimpleConfigObject({"test": "success"})
scala> val value = config.getString("test")
value: String = test

Why does custom scaladoc task throw MissingRequirementError: object scala.annotation.Annotation in compiler mirror not found?

I hit a MissingRequirementError when I try to invoke scaladoc from within an sbt task.
Using any version of sbt 0.13.x, start with this build.sbt:
val scaladoc = taskKey[Unit]("run scaladoc")
scaladoc := {
import scala.tools.nsc._
val settings = new doc.Settings(error => print(error))
settings.usejavacp.value = true
val docFactory = new doc.DocFactory(new reporters.ConsoleReporter(settings), settings)
val universe = docFactory.makeUniverse(Left((sources in Compile).value.map(_.absolutePath).toList))
}
Then run sbt scaladoc, and behold (during makeUniverse):
[info] Set current project to test (in build file:...)
scala.reflect.internal.MissingRequirementError: object scala.annotation.Annotation in compiler mirror not found.
at scala.reflect.internal.MissingRequirementError$.signal(MissingRequirementError.scala:16)
at scala.reflect.internal.MissingRequirementError$.notFound(MissingRequirementError.scala:17)
at scala.reflect.internal.Mirrors$RootsBase.getModuleOrClass(Mirrors.scala:48)
What is wrong here? I've already tried fork := true and different combinations of sbt/scala versions to no avail.
It seems you need to provide scala-library (and indeed, any other dependencies) directly to the DocFactory.
scaladoc := {
import scala.tools.nsc._
val settings = new doc.Settings(error => print(error))
val dependencyPaths = (update in Compile).value
.select().map(_.absolutePath).mkString(java.io.File.pathSeparator)
settings.classpath.append(dependencyPaths)
settings.bootclasspath.append(dependencyPaths)
val docFactory = new doc.DocFactory(new reporters.ConsoleReporter(settings), settings)
val universe = docFactory.makeUniverse(Left((sources in Compile).value.map(_.absolutePath).toList))
}

sbt runs IMain and play makes errors

I've build a litte object, which can interpret scala code on the fly and catches a value out of it.
object Interpreter {
import scala.tools.nsc._
import scala.tools.nsc.interpreter._
class Dummy
val settings = new Settings
settings.usejavacp.value = false
settings.embeddedDefaults[Dummy] // to make imain useable with sbt.
val imain = new IMain(settings)
def run(code: String, returnId: String) = {
this.imain.beQuietDuring{
this.imain.interpret(code)
}
val ret = this.imain.valueOfTerm(returnId)
this.imain.reset()
ret
}
}
object Main {
def main(args: Array[String]) {
println(Interpreter.run("val x = 1", "x"))
}
}
In a pure sbt environment or called by the scala interpreter this code works fine! But if I run this in a simple play (version 2.2.2) application, it gets a null pointer at val ret = this.imain.valueOfTerm(returnId).
play uses also a modified sbt, therefor it should probably work. What does play do that this code doesn't work anymore? Any ideas how to get this code to work in play?
Note
That's the used build.sbt:
name := "Test"
version := "1.0"
scalaVersion := "2.10.3"
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
Alternatively I tried this implementation, but it doesen't solve the problem either:
object Interpreter2 {
import scala.tools.nsc._
import scala.tools.nsc.interpreter._
import play.api._
import play.api.Play.current
val settings: Settings = {
lazy val urls = java.lang.Thread.currentThread.getContextClassLoader match {
case cl: java.net.URLClassLoader => cl.getURLs.toList
case _ => sys.error("classloader is not a URLClassLoader")
}
lazy val classpath = urls map {_.toString}
val tmp = new Settings()
tmp.bootclasspath.value = classpath.distinct mkString java.io.File.pathSeparator
tmp
}
val imain = new IMain(settings)
def run(code: String, returnId: String) = {
this.imain.beQuietDuring {
this.imain.interpret(code)
}
val ret = this.imain.valueOfTerm(returnId)
this.imain.reset()
ret
}
}
Useful links I found to make this second implementation:
scala.tools.nsc.IMain within Play 2.1
How to set up classpath for the Scala interpreter in a managed environment?
https://groups.google.com/forum/#!topic/scala-user/wV86VwnKaVk
https://github.com/gourlaysama/play-repl-example/blob/master/app/REPL.scala#L18
https://gist.github.com/mslinn/7205854
After spending a few hours on this issue myself, here is a solution that I came up with. It works both inside SBT and outside. It is also expected to work in a variety of managed environments (like OSGi):
private def getClasspathUrls(classLoader: ClassLoader, acc: List[URL]): List[URL] = {
classLoader match {
case null => acc
case cl: java.net.URLClassLoader => getClasspathUrls(classLoader.getParent, acc ++ cl.getURLs.toList)
case c => LOGGER.error("classloader is not a URLClassLoader and will be skipped. ClassLoader type that was skipped is " + c.getClass)
getClasspathUrls(classLoader.getParent, acc)
}
}
val classpathUrls = getClasspathUrls(this.getClass.getClassLoader, List())
val classpathElements = classpathUrls map {url => url.toURI.getPath}
val classpath = classpathElements mkString java.io.File.pathSeparator
val settings = new Settings
settings.bootclasspath.value = classpath
val imain = new IMain(settings)
// use imain to interpret code. It should be able to access all your application classes as well as dependent libraries.
It's because play uses the "fork in run" feature from sbt. This feature starts a new JVM and this causes that this failure appears:
[info] Failed to initialize compiler: object scala.runtime in compiler mirror not found.
[info] ** Note that as of 2.8 scala does not assume use of the java classpath.
[info] ** For the old behavior pass -usejavacp to scala, or if using a Settings
[info] ** object programatically, settings.usejavacp.value = true.
See: http://www.scala-sbt.org/release/docs/Detailed-Topics/Forking