SBT: add command-line parsing to modify settings for existing tasks - scala

When running tasks (e.g., test, jmh:run), I often want to specify javaOptions which are tedious to type by hand (e.g., to dump program data).
This is my current approach:
// build.sbt
lazy val myProject = project
...
.settings(
...
Test / javaOptions ++= if (sys.props.get("dump").nonEmpty) Seq("-X...", ...) else Nil
)
I can set system properties on sbt launch (e.g., sbt -Ddump) and then check them with sys.props, but changing these properties requires me to reload sbt. I would like to parse some arguments when the task is invoked, such that I can write test -dump and modify the Test / javaOptions setting accordingly.
Is this possible? Someone recommended I override the default task but I'm having trouble figuring out what that would look like. I have a suspicion I need an InputTask for this, but also don't know what that'd look like.

You don’t need to reload sbt, if you are running sbt in a shell, you will need to programtically call‚ sys.props.set

Related

How to set SCALACTIC_FILL_FILE_PATHNAMES=yes (or other environment variable) for scalac from sbt?

I would like to use full paths in ScalaTest reporter. The value LineInFile.filePathname tells me I should:
Please set the environment variable SCALACTIC_FILL_FILE_PATHNAMES to yes at compile time to enable this feature.
Can I do this from sbt, or do I have to launch SBT with a different environment? Scalac seems to be running in the same JVM as sbt, and there is a question Scala: Unable to set environment variable telling me I cannot modify my own environment.
I have tried following, none of them seems to set the variable for the compiler:
scalacOptions ++= Seq(
"-deprecation", "-unchecked",
"-DSCALACTIC_FILL_FILE_PATHNAMES=yes"
),
Compile / envVars := Map("SCALACTIC_FILL_FILE_PATHNAMES" -> "yes"),
Test / envVars := Map("SCALACTIC_FILL_FILE_PATHNAMES" -> "yes"),
As far as I know, sbt doesn't run scalac in a separate process (which would allow it to set environment variables), so you'd have to run sbt in an environment with that set.
Note that -D sets Java properties. While it's fairly common for software to allow environment variables and properties to be treated similarly (e.g. Lightbend Config), that's not standard.
The mechanism for setting the environment will depend on how you are launching sbt. If using a Bourne-style shell like bash:
export SCALACTIC_FILL_FILE_PATHNAMES=yes
sbt
or
SCALACTIC_FILL_FILE_PATHNAMES=yes sbt
IDEs and CI/CD tooling may launch sbt directly, in which case check the documentation for those tools.

In SBT, how do you override compile to run arbitrary code afterwards?

I'm trying to use SBT to build a project that depends on bytecode enhancement. Basically, I need to run some code after compile using the classpath in the current scope (so the command can find the classes to modify), and then make sure that compile doesn't run again afterwards to undo the enhancement.
I'm using SBT 0.13.12, if that matters.
I believe you would want to make a new sbt task and have it depend on compile. Then use that rather than compile.
lazy val bytecodeEnhancedCompile = taskKey[Unit]("bytecode Enhance")
bytecodeEnhancedCompile <<= bytecodeEnhancedCompile dependsOn (compile in Compile)
bytecodeEnhancedCompile := {
....
}

SBT cleanup hook in test

SBT has a nice hook which allows you to execute arbitrary code after all tests are run:
testOptions in Test += Tests.Cleanup( () => println("Cleanup"))
That works.
My question is: I want to do some actual cleanup (stopping some services for example) but I can't import any dependencies which I've declared in the same build file. Is there any way to do this? I guess I need to put these on the sbt classpath or something, but I can't seem to find this in the docs.
P.S. I might be doing this in the wrong location, is there a better place to shutdown things after all tests are run?)
Complementing venechka's answer: I'm running integration tests using Specs2, and in specs there is no way of knowing when all tests have run. So I solved it pretty much the way venechka and you yourself already indicated, by loading a class from the project that does the cleanup when it's initialized:
testOptions in IntegrationTest += Tests.Cleanup( (loader: java.lang.ClassLoader) => {
loader.loadClass("com.mypackage.IntegrationTestCleanup").newInstance
} )
You can't use the classes that are added with libraryDependencies in project (you can add libraryDependencies in project/project, but I wouldn't recommend adding in 2 places). Instead you can invoke a cleanup method that is in your project's sources, and that has access to the declared library dependencies.

How to specify JVM maximum heap size "-Xmx" for running an application with "run" action in SBT?

My application does large data arrays processing and needs more memory than JVM gives by default. I know in Java it's specified by "-Xmx" option. How do I set SBT up to use particular "-Xmx" value to run an application with "run" action?
For forked processes you should look at Build.scala
To modify the java options for forked processes you need to specify them in the Build.scala (or whatever you've named your build) like this:
val buildSettings = Defaults.defaultSettings ++ Seq(
//…
javaOptions += "-Xmx1G",
//…
)
This will give you the proper options without modifying JAVA_OPTS globally, and it will put custom JAVA_OPTS in an sbt generated start-script
For non forked processes it's most convenient to set the config via sbtopts or sbtconfig depending on your sbt version.
Since sbt 0.13.6 .sbtconfig is deprecated. Modify /usr/local/etc/sbtopts along these lines:
-J-Xms512M
-J-Xmx3536M
-J-Xss1M
-J-XX:+CMSClassUnloadingEnabled
-J-XX:+UseConcMarkSweepGC
-J-XX:MaxPermSize=724M
-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
You can also create an .sbtopts file in the root of your SBT project using the same syntax as in the /usr/local/etc/sbtopts file. This makes the project self-contained.
Before sbt 0.13.6 you could set the options in .sbtconfig for non forked processes:
Check where sbt is:
$ which sbt
/usr/local/bin/sbt
Look at the contents:
$ cat /usr/local/bin/sbt
#!/bin/sh
test -f ~/.sbtconfig && . ~/.sbtconfig
exec java ${SBT_OPTS} -jar /usr/local/Cellar/sbt/0.12.1/libexec/sbt-launch.jar "$#"
Set the correct jvm options to prevent OOM (both regular and PermGen):
$ cat ~/.sbtconfig
SBT_OPTS="-Xms512M -Xmx3536M -Xss1M
-XX:+CMSClassUnloadingEnabled
-XX:+UseConcMarkSweepGC -XX:MaxPermSize=724M"
If you want to set SBT_OPTS only for the current run of sbt you can use env SBT_OPTS=".." sbt as suggested by Googol Shan. Or you can use the option added in Sbt 12: sbt -mem 2048. This gets unwieldy for longer lists of options, but it might help if you have different projects with different needs.
Note that CMSClassUnloadingEnabled in concert with UseConcMarkSweepGC helps keep the PermGen space clean, but depending on what frameworks you use you might have an actual leak on PermGen, which eventually forces a restart.
In sbt version 12 onwards there is an option for this:
$sbt -mem 2048
If you run sbt on linux shell, you can use:
env JAVA_OPTS="-Xmx512m" sbt run
This is my usually used command to run my sbt project.
.sbtconfig is deprecated starting with SBT 0.13.6. Instead, I configured these options in /usr/local/etc/sbtopts in the following way:
-J-Xms512M
-J-Xmx3536M
-J-Xss1M
-J-XX:+CMSClassUnloadingEnabled
-J-XX:+UseConcMarkSweepGC
-J-XX:MaxPermSize=724M
-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Try this:
class ForkRun(info: ProjectInfo) extends DefaultProject(info) {
override def fork = Some(new ForkScalaRun {
override def runJVMOptions = super.runJVMOptions ++ Seq("-Xmx512m")
override def scalaJars = Seq(buildLibraryJar.asFile, buildCompilerJar.asFile)
})
}
There's one way I know of. Set the environment variable JAVA_OPTS.
JAVA_OPTS='-Xmx512m'
I have not found a way to do this as a command parameter.
Use JAVA_OPTS for setting with environment variable.
Use -J-X options to sbt for individual options, e.g. -J-Xmx2048 -J-XX:MaxPermSize=512
Newer versions of sbt have a "-mem" option.
The javaOptions += "-XX:MaxPermSize=1024" in our build.sbt as referenced by #iwein above worked for us when we were seeing a java.lang.OutOfMemoryError thrown while running Specs2 tests through sbt.
The environment variable is _JAVA_OPTIONS, which needs to be set.
Once you set _JAVA_OPTIONS, and when you sbt, sbt will show the message using JAVA_OPTIONS and the values.
Alternatively you could set javaOption in the sbt or .scala file
e.g
javaOptions += "-Xmx1G"
From sbt shell you could run show javaOptions to see the values that are set.
sbt lets you list the JVM options you need to run your project on a file named
.jvmopts
in the root of your project.
then add the java options that you want
cat .jvmopts
-Xms512M
-Xmx4096M
-Xss2M
-XX:MaxMetaspaceSize=1024M
it is tested and works in windows 10
https://www.lagomframework.com/documentation/1.4.x/scala/JVMMemoryOnDev.html
javaOptions in Test += "-Xmx1G"
This sets the JVM options for tests. Works also with jvm forking (fork in Test := true).

How do I set a system property for my project in sbt?

I'm sure I'm missing something really simple... I want to set the system property java.awt.headless to true for my sbt project. Reading the page on properties I think that I need to use system or systemOptional. In my project file I've tried things like:
lazy val javaAwtHeadless = system[Boolean]("java.awt.headless")
Setting it as a user property (e.g. lazy val javaAwtHeadless = property[Boolean]) and setting the accompanying value in build.properties made the property visible in the sbt console but not within sbt's Scala console (via System.getProperty("java.awt.headless")).
set java.awt.headless true from the sbt console works, including being set in the Scala console, but it doesn't persist to the next time I launch sbt.
A straightforward method would be to edit the batch file or shell script that you use to run sbt and add -Dprop=val
If I needed this option for all sbt tasks, I'd set it as follows in build.sbt
javaOptions += "-Djava.awt.headless=true"
If it was just for one task, eg: run, you can scope that:
javaOptions in Runtime += "-Djava.awt.headless=true"
If you're trying to set SBT properties, like plugin settings, then the following worked for me with 0.13+. The following however did work, when trying to pass in Liquibase settings, like password, from our CI frameworks.
In your build.sbt
Ugly, but supplies defaults, and optionally grabs from System.properties. This way you've got your default and override cases covered.
def sysPropOrDefault(propName:String,default:String):String = Option(System.getProperty(propName)).getOrElse(default)
liquibaseUsername := sysPropOrDefault("liquibase.username","change_me")
liquibasePassword := sysPropOrDefault("liquibase.password","chuck(\)orris")
From the commandline
Now just override via -Dprop=value like you would with Maven or other JVM programs. Note props appear before SBT task.
sbt -Dliquibase.password="shh" -Dliquibase.username="bob" liquibase:liquibase-update