Set mainClass for sbt native packager Universal - scala

I have a project that has the following build.sbt:
addCommandAlias("package", "dist")
lazy val actual = (project in file("."))
.enablePlugins(UniversalPlugin, JavaServerAppPackaging)
.settings(
name := "DeployerPod",
mainClass := Some("com.myself.executable.Runner"),
Compile / mainClass := Some("com.myself.executable.Runner"),
Compile / run / mainClass := Some("com.myself.utils.Pipeline"),
Universal / mainClass := Some("com.myself.executable.Runner"),
Universal / compile / mainClass := Some("com.myself.executable.Runner"),
)
We have a CICD which runs a Dockerfile.
There I have sbt run as one of the steps, which will execute com.myself.utils.Pipeline class to run a Scala class and do the pre requisites for the pipeline.
As one of the last sbt based steps, I'm also running sbt package, which eventually runs an sbt dist command. At this point, inside the extracted ZIP's bin folder, I see two BAT files corresponding to the two main classes. Unfortunately I only want the Runner class BAT instead of Pipeline BAT.
For this I tried running sbt package -main com.myself.executable.Runner but that failed saying Not a valid command: -
Is there a way I can specify the mainClass only for this Universal plugin somehow? Because the way I've tried in my build.sbt doesn't seem to work.

Related

sbt-assembly - can not find main class in jar

I have a project where I am trying to create a fat jar using the sbt-assembly plugin. When I attempt to run my main class from the jar using the java -jar command, I get the error message: Error: Could not find or load main class com.gordon.timeshare.apps.TimeShareLauncher.
I only have one main class in my project (I use the extends App syntax to accomplish this), so I do not specify the path to the main class explicitly, although I have tried that and it did not help.
Below are all the settings I have in my build.sbt file.
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.gordon.timeshare.apps"
ThisBuild / scalaVersion := "2.13.5"
lazy val app = (project in file("app"))
.settings(
assembly / mainClass := Some("com.gordon.timeshare.apps.TimeShareLauncher"),
assembly / assemblyJarName := "TimeShareLauncher.jar"
)
assemblyMergeStrategy in assembly := {
case PathList("META-INF", xs # _*) => MergeStrategy.discard
case x => MergeStrategy.first
}
I have also tried other strategies like deduplicate, but that would give me an error when trying to make the .jar.
Additionally, when making the .jar, I get a warning:
[warn] Could not create directory C:\Users\dgord\workspace\new-timeshare\timeshare\target\streams\_global\assembly\_global\streams\assembly\88fbe735ce5abc6987fbc59b072404628cdc94b4_a99f2fe2a42747ed9809d4f62f51a9e1b336dde8_da39a3ee5e6b4b0d3255bfef95601890afd80709\META-INF\versions\9: java.nio.file.FileAlreadyExistsException: C:\Users\dgord\workspace\new-timeshare\timeshare\target\streams\_global\assembly\_global\streams\assembly\88fbe735ce5abc6987fbc59b072404628cdc94b4_a99f2fe2a42747ed9809d4f62f51a9e1b336dde8_da39a3ee5e6b4b0d3255bfef95601890afd80709\META-INF\versions\9
And in case you want to know what my main class looks like:
package com.gordon.timeshare.apps
object TimeShareLauncher extends App
sbt: 1.4.7 (also tried 1.5.5)
sbt-assembly: 1.1.0
scala 2.13.5
I have also tried this on WSL and had the same result.
The issue is with lazy val app = (project in file("app")). Assuming a single module project with no module named app, sbt-assembly will create a directory named app and attempt to stuff the build in there. However, since the main class is not in the app bundle, the class will not be added to the jar file.
The correct way to do this is:
lazy val app = (project in file(".")), which specifies the current directory as the one to look for the main class. So this was not really an issue with knowing how to use the sbt-assembly plugin, but a more general issue with specifying projects in an sbt build.

sbt plugin: add an unmanaged jar file

I'm trying to create a relatively simple sbt plugin to wrap grpc-swagger artifact.
Therefore, I've created a project with the following structure:
projectDir/
build.sbt
lib/grpc-swagger.jar <- the artifact I've downloaded
src/...
where build.sbt looks like the following:
ThisBuild / version := "0.0.1-SNAPSHOT"
ThisBuild / organization := "org.testPlugin"
ThisBuild / organizationName := "testPlugin"
lazy val root = (project in file("."))
.enable(SbtPlugin)
.settings(name := "grpc-swagger-test-plugin")
According to sbt docs, that's all I have to do in order to include an unmanaged dependecy, that is:
create a lib folder;
store the artifact in there;
However, when I do execute sbt compile publishLocal, the plugin published lacks of that external artifact.
So far I've tried to:
set exportJars := true flag
add Compile / unmanagedJars += file(lib/grpc-swagger.jar") (with also variations of the path)
manual fiddling to libraryDependecies using from file("lib/grpc-swagger.jar") specifier
but none so far seemed to work.
So how am I supposed to add an external artifact to a sbt plugin?
The proper solution to this problem is to publish the grpc-swagger library. If for whatever reason this can't be done from that library's build system, you can do it with sbt. Just add a simple subproject whose only job it is to publish that jar. It should work like so:
...
lazy val `grpc-swagger` = (project in file("."))
.settings(
name := "grpc-swagger",
Compile / packageBin := baseDirectory.value / "lib" / "grpc-swagger.jar",
// maybe other settings, such as grpc-swagger's libraryDependencies
)
lazy val root = (project in file("."))
.enable(SbtPlugin)
.settings(name := "grpc-swagger-test-plugin")
.dependsOn(`grpc-swagger`)
...
The pom file generated for the root project should now specify a dependency on grpc-swagger, and running the publish task in the grpc-swagger project will publish that jar along with a pom file.
That should be enough to make things work, but honestly, it's still a hack. The proper solution is to fix grpc-swagger's build system so you can publish an artifact from there and then just use it via libraryDependencies.

SBT-java.lang.RuntimeException: No main class detected

sbt compile gives Success
sbt run gives the error mentioned above.
My Directory Structure is a little bit different from the regular SBT structure:
Directory structure that I need... Build.sbt inside main project
Build.sbt inside SubProject
MainClass.scala
object MainClass extends App {
println("Hello world!")
}
Note: Things I have already tried in Build.sbt of main project:
1. scalaSource in (Compile, run) := baseDirectory.value / "App" / "js"
2. mainClass in (Compile, run) := Some("MainClass")
3. mainClass in (Compile, run) := Some("App/js/src/main/scala/MainClass")
I am not able to figure out the mistake?
It is not possible to declare additional projects in .sbt files that are in subdirectories. All projects have to be declared in .sbt files at the root of build.
This means that your AppJs and AppJvm never get to have any effect, and those projects do not actually exist in your build.
You'll have to declare AppJs, AppJvm, and any other project you need in the top-level build.sbt file.

sbt console - set scala-version for all sub-projects

I have a multi-project build with scalaVersion := "2.11.8" for each module.
I want to run test and publish-local for Scala 2.12.0-RC1 without touching the build file. I thought the following would work:
$ sbt
> set scalaVersion in ThisBuild := "2.12.0-RC1"
> test
But that does not alter the Scala version used, sbt still compiles with Scala 2.11.8. This only works for single module builds (without project definitions).
How do I effectively override scalaVersion for all sub-modules from the sbt console?
Your attempt doesn't work because the setting for the module takes priority over the setting for ThisBuild; it would work if you used scalaVersion in ThisBuild in the build file instead of setting it separately for each module. I don't know if there is a way to do this with anything except scalaVersion, but:
As a final note, you can use ++ to temporarily switch the Scala version currently being used to build. should be either a version for Scala published to a repository, as in ++ 2.10.0 or the path to a Scala home directory, as in ++ /path/to/scala/home. See Command Line Reference for details.
> ++2.12.0-RC1
> test

How to use SBT to run ScalaTest tests against a fat jar?

I have a simple SBT project, consisting of some Scala code in src/main/scala and some test code in src/test/scala. I use the sbt-assembly plugin to create a fat jar for deployment onto remote systems. The fat jar includes all the dependencies of the Scala project, including the Scala runtime itself. This all works great.
Now I'm trying to figure out a way I can run the Scala tests against the fat jar. I tried the obvious thing, creating a new config extending the Test config and modifying the dependencyClasspath to be the fat JAR instead of the default value, however this fails because (I assume because) the Scala runtime is included in the fat jar and collides somehow with the already-loaded Scala runtime.
My solution right now works but it has serious drawbacks. I just use Fork.java to invoke Java on the org.scalatest.tools.Runner runner with a classpath set to include the test code and the fat jar and all of the test dependencies. The downside is that none of the SBT test richness works, there's no testQuick, there's not testOnly, and the test failure reporting is on stdout.
My question boils down to this: how does one use SBT's test commands to run tests when those tests are dependent not on their corresponding SBT compile output, but on a fat JAR file which itself includes all the Scala runtimes?
This is what I landed on (for specs2, but can be adapted). This is basically what you said was your Fork solution, but I figured I'd leave this here in case someone wanted to know what that might be. Unfortunately I don't think you can run this "officially" as a SBT test runner. I should also add that you still want Fork.java even though this is Scala, because Fork.scala depends on a runner class that I don't seem to have.
test.sbt (or build.sbt, if you want to put a bunch of stuff there - SBT reads all .sbt files in the root if you want to organize):
// Set up configuration for building a test assembly
Test / assembly / assemblyJarName := s"${name.value}-test-${version.value}.jar"
Test / assembly / assemblyMergeStrategy := (assembly / assemblyMergeStrategy).value
Test / assembly / assemblyOption := (assembly / assemblyOption).value
Test / assembly / assemblyShadeRules := (assembly / assemblyShadeRules).value
Test / assembly / mainClass := Some("org.specs2.runner.files")
Test / test := {
(Test / assembly).value
val assembledFile: String = (Test / assembly / assemblyOutputPath).value.getAbsolutePath
val minimalClasspath: Seq[String] = (Test / assembly / fullClasspath).value
.filter(_.metadata.get(moduleID.key).get.organization.matches("^(org\\.(scala-lang|slf4j)|log4j).*"))
.map(_.data.getAbsolutePath)
val runClass: String = (Test / assembly / mainClass).value.get
val classPath: Seq[String] = Seq(assembledFile) ++ minimalClasspath
val args: Seq[String] = Seq("-cp", classPath.mkString(":"), runClass)
val exitCode = Fork.java((Test / assembly / forkOptions).value, args)
if (exitCode != 0) {
throw new TestsFailedException()
}
}
Test / assembly / test := {}
Change in build.sbt:
lazy val root = (project in file("."))
.settings(/* your original settings are here */)
.settings(inConfig(Test)(baseAssemblySettings): _*) // enable assembling in test