Can sbt-native-packager generate multiple start scripts for one project? - scala

I'm currently using sbt-native-packager to generate a start script for my scala application. I'm using packageArchetype.java_application. I create the script in sbt:
sbt clean myproject/stage
and then "install" the application by copying the created lib and bin directories to the installation directory. I'm not distributing it to anyone, so I'm not creating an executable jar or tarball or anything like that. I'm just compiling my classes, and putting my jar and all the library dependency jars in one place so the start script can execute.
Now I want to add a second main class to my application, so I want a second start script to appear in target/universal/stage/bin when I run sbt stage. I expect it will be the same script but with a different name and app_mainclass set to the different class. How would I do this?

The sbt-native-packager generated script allows you to pass in a -main argument to specify the main class you want to run. Here's what I do for a project named foo:
Create a run.sh script with whatever common options you want that calls the sbt-native-packager generated script:
#!/bin/bash
./target/universal/stage/bin/foo -main "$#"
Then I create a separate script for each main class I want to run. For example first.sh:
#!/bin/bash
export JAVA_OPTS="-Xms512m -Xmx512m"
./run.sh com.example.FirstApp -- "$#"
and second.sh:
#!/bin/bash
export JAVA_OPTS="-Xms2048m -Xmx2048m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC"
./run.sh com.example.SecondApp -- "$#"

Having multiple main classes is... supported now (Q4 2016, native package 1.2.0)
See "SBT Native Packager 1.2.0" by Muki Seiler
Single Project — Multiple Apps
A major pain point for beginners is the start script creation. The bash and bat start scripts are only generated when there is a either
Exactly one main class
Explicitly set main class with
mainClass in Compile := Some(“com.example.MainClass”)
For 1.2.x we will extends the implementation and support multiple main classes by default.
Native packager will generate a start script for each main class found on the classpath.
SBT provides them via the discoveredMainClasses in Compile task.
If there is only one main class, SBT will assign it to the mainClass in Compile setting. This leads to three cases:
Exactly one main class.
In this case native-packager will behave like previous versions and just generate a single start script, using the executableScriptName setting for the script name.
Multiple main classes and mainClass in Compile := None.
This is the default behaviour defined by SBT. In this case native-packager will generate the same start script for each main class.
Multiple main classes and mainClass in Compile := Some(…).
The user has set a specific main class, which will lead to a main start script being generated using the executableScriptName setting. For all other main classes native-packager generates forwarder scripts.

Having multiple main classes not supported now. As a workaround you could use single main class and check command line args.
Starting your app:
myApp prog1
In your main class:
def main(args: Array[String]): Unit = {
if(args[0] == "prog1")
Programm1.start()
else
Programm2.start()
}

Related

How can I get the build output directory inside tests run by sbt?

I need to create directories and files for some tests. My project uses sbt as the build tool, and common practice is to use File.createTempFile or similar APIs, but I abhor that practice. I want all files created by my tests to reside somewhere inside the output directory (<module>/target/), so that they'll be removed when I run clean, but otherwise preserved if I have need of them to figure out test failures.
The test framework is not relevant: if your solution requires a particular framework, I'll happily adopt it or figure out how it does the trick and use that.
In short, I need the answer to one of these two questions:
How can I create a file inside the build output directory from a test run by sbt?
How can I find out what is the build output directory for the current project from a test run by sbt?
In ScalaTest, try passing target
settingKey[File]("Main directory for files generated by the build.")
to config map as -Dkey=value. For example, in build.sbt specify
Test / testOptions += Tests.Argument(s"-DtargetDir=${target.value}")
and then define test like so
import org.scalatest._
class ExampleSpec extends fixture.FlatSpec with fixture.ConfigMapFixture with Matchers {
"The config map" should "contain target directory used by sbt" in { configMap =>
configMap should contain key "targetDir"
}

Where do SBT env variables come from?

I'm moving my first steps with Scala (2.12.7) and SBT (1.2.7).
At some point, I want to get secret value from the environment:
sys.env("SECRET_TOKEN")
The problem is that, in the sbt shell, SECRET_TOKEN is not defined, therefore the application crashes.
So:
$ export SECRET_TOKEN="xxx"
$ sbt
[... sbt loads]
sbt> run
[ crashes because of the env var not found ]
It's like the sbt shell would get only a subset of the current environment.
Am I missing something?
Thanks
sbt (script + launcher) just launches a fancy java process, which should inherit the environment variables from the parent process.
Given
$ export SECRET_TOKEN="xxx"
Both build.sbt and your application during run should have access to sys.env("SECRET_TOKEN").
In the comment section, Bruno suggested SECRET_TOKEN="xxx" sbt, which apparently worked, but I don't know how that's different from export.
In any case,
object Hello extends App {
println(sys.env("SECRET_TOKEN"))
}
works for me
sbt:hello> run
[info] Running Hello
xxx

How to create new commands that you can run after running the sbt command Scala

I'm trying to run certain tasks and startup servers after running sbt. I want to be able to run commands in terminal to do this. How can I define them? Are plugins the right way to do this:
I see some code like this:
object DoThing extends AutoPlugin {
object autoImport {
val vpnCheck = taskKey[Boolean]("Check for a VPN connection.")
}
import autoImport._
override lazy val projectSettings = Seq(
vpnCheck := {
doVpnCheck()
}
)
What is the projectSettings method doing? Are plugins the way?
From the plugins page:
A plugin is a way to use external code in a build definition. A plugin can be a library used to implement a task (you might use Knockoff to write a markdown processing task). A plugin can define a sequence of sbt settings that are automatically added to all projects or that are explicitly declared for selected projects. For example, a plugin might add a proguard task and associated (overridable) settings. Finally, a plugin can define new commands (via the commands setting).
But I can't seem to figure this out.
For your scenario, maybe you can just a create Task in sbt file to do this, like:
val hello = taskKey[Unit]("hello world")
hello := {
println("hello")
}
and if you run it automatically in startup time, you can create .sbtrc file in project directory, and it like:
alias boot = ;reload ;hello ;iflast shell

Manually exclude some test classes in sbt

I usually do below command in my CI:
clean update compile test publish
However, I'd like to exclude 1 (or a few) test class from the sbt command line.
How can I do this? (I don't want to change my code to use ignore, etc)
Two possible options
test-only See http://www.scalatest.org/user_guide/using_scalatest_with_sbt
Tags http://www.scalatest.org/user_guide/tagging_your_tests
Just to elaborate on the 2 correct options #Gonfva suggested above:
To use testOnly you should run:
sbt "testOnly org.fully.qualified.domainn.name.ASpec"
When the argument is the FQDN of the class. You can use multiple classes separate them by space. This can be used with glob as well. For example:
sbt "testOnly *ASpec"
Using tags. First, define a tag:
import org.scalatest.Tag
object CustomTag extends Tag("tagName")
Then, define a test with this tag:
it should "test1" taggedAs CustomTag in { println("test1") }
Now, in order to include tests using this tag, run:
sbt "testOnly * -- -n tagName"
Note: * is a wild card. It can be any glob as described in section 1.
In order to exclude this tag, you need to run:
sbt "testOnly * -- -l aaa"
Including or excluding tests is dependent on the test framework you are using. The command you will be employing in SBT is not completely parsed by SBT, but partially parsed by SBT, partially parsed by the test framework you are using.
So, if you are suing scalameta/munit, you may enter a command like this:
sbt> myproject/Test/testOnly MyProjectTest -- --exclude-tags=tag1,tag2,tag3
If you are using another test framework, the specific syntax after -- will be probably different.
When you are writing your test cases, you have to add tags somewhere, obviously. More details can be found below:
scalameta/munit: https://scalameta.org/munit/docs/filtering.html
scalatest: https://www.scalatest.org/user_guide/tagging_your_tests (stolen from another answer)

Using simple-build-tool for benchmarks

I'm trying to get sbt to compile and build some benchmarks. I've told it to add the benchmarks to the test path so they're recompiled along with tests, but I can't figure out how to write an action to let me actually run them. Is it possible to invoke classes from the Project definition class, or even just from the command line?
Yes, it is.
If you'd like to run them in the same VM the SBT is run in, then write a custom task similar to the following in your project definition file:
lazy val benchmark = task {
// code to run benchmarks
None // Some("will return an error message")
}
Typing benchmark in SBT console will run the task above. To actually run the benchmarks, or, for that matter, any other class you've compiled, you can reuse some of the existing infrastructure of SBT, namely the method runTask which will create a task that runs something for you. It has the following signature:
def runTask(mainClass: => Option[String], classpath: PathFinder, options: String*): Task
Simply add the following to your file:
lazy val benchmark = task { args =>
runTask(Some("whatever.your.mainclass.is"), testClasspath, args)
}
When running benchmarks, it is sometimes recommended that you run them in a separate jvm invocation, to get more reliable results. SBT allows you to run separate processes by invoking a method ! on a string command. Say you have a command java -jar path-to-artifact.jar you want to run. Then:
"java -jar path-to-artifact.jar" !
runs the command in SBT. You want to put the snippet above in a separate task, same as earlier.
And don't forget to reload when you change your project definition.
Couldn't you simply write the benchmarks as tests, so they will be run when you call 'test' in SBT?
You could also run a specific test with 'test-only', or run a main with 'run' or 'exec' (see http://code.google.com/p/simple-build-tool/wiki/RunningSbt for details).