How to create new SBT configuration with its own Main? - sbt

I'm trying to create a new Configuration in an SBT Scala project with its own main class. Here are the basic requirements:
Production application code is located in <project root>/src/main/scala/com/example/Main.scala:
package com.example
object Main extends App {
println("Hello from Main!")
}
The main class I'm trying to run should be located in <project root>/src/qa/scala/com/example/QAMain.scala:
package com.example
object QAMain extends App {
println("Hello from QA!")
}
(As suggested by the path, the actual use-case for this is a version of the application for QA to run that bypasses certain time-consuming operations.)
This main class should be runnable by executing sbt qa:run in the project root directory.
(Nice to have): The classpath of the running application should not contain any of the test classes defined under src/test.
Here's a build.sbt that I feel ought to work, but doesn't:
lazy val QA = config("qa").extend(Compile)
lazy val root = project.in(file("."))
.configs(QA)
.settings(
(sourceDirectories in (QA, compile)) += baseDirectory.value / "src" / "qa",
(mainClass in (QA, run)) := Some("com.example.QAMain"),
(mainClass in Compile) := Some("com.example.Main")
)
Unfortunately, the result is
> sbt qa:run
...
[info] Running playground.Main
Hello from Main!
> sbt "qa:runMain com.example.QAMain"
...
[info] Running com.example.QAMain
[error] (run-main-0) java.lang.ClassNotFoundException: com.example.QAMain
[error] java.lang.ClassNotFoundException: com.example.QAMain
[error] at java.lang.ClassLoader.findClass(ClassLoader.java:530)
[error] at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
[error] Nonzero exit code: 1
[error] (Compile / runMain) Nonzero exit code: 1
That last line is interesting, because it looks like SBT is running the task scoped to the Compile configuration instead of my custom QA configuration. sbt inspect confirms this.
My assumption has been that since a configuration's compile task uses the sourceDirectory setting, that an override of that setting will force an override of any task downstream of that setting. This assumption might be wrong in a couple of different ways:
sourceDirectory may not be upstream of compile, but maybe some other setting is that I could change;
compile might need to be explicitly overridden anyway.

It's not clear which-all settings were upstream of compile, but there's apparently enough of them that you need to use sbt.Defaults:
lazy val QA = config("qa").extend(Compile)
lazy val root = project.in(file("."))
.configs(QA)
.settings(
inConfig(QA)(Defaults.compileSettings) : _*
)
This achieves the desired behavior. To test this, add the following classes:
<project root>/src/main/scala/com/example/Water.scala
package com.example
class Water {
def drink(): Unit = {
println("Cool and refreshing")
}
}
<project root>/src/qa/scala/com/example/Poison.scala
package com.example
class Poison {
def drink(): Unit = {
println("You have died of dysentery.")
}
}
Then SBT is happy to build an instance of Water into QAMain, but will not find Poison to build into Main.

Related

Running sbt release task from cli for sub-project doesn't work

I have a multi-project sbt where I use sbt-release plugin. Everything works fine if I run release in a sub-project
> project reporter
[info] Set current project to reporter (in build file:/source/storage-integ/)
> release
[info] Starting release process off commit: c069698baf8bb6fca611ab4e7e086398aab473c5
[info] Checking remote [origin] ...
But this doesn't work when I run "sbt reporter/release" from cli. Where as "sbt reporter/compile" or "sbt reporter/assembly" do work.
$ sbt reporter/release
[warn] Executing in batch mode.
[warn] For better performance, hit [ENTER] to switch to interactive mode, or
[warn] consider launching sbt without any commands, or explicitly passing 'shell'
[info] Loading global plugins from /home/vagrant/.sbt/0.13/plugins
[info] Loading project definition from /source/storage-integ/project
[info] Set current project to root (in build file:/source/storage-integ/)
[error] Expected ':' (if selecting a configuration)
[error] Not a valid key: release (similar: releaseVcs, rpmRelease, rpm-release)
[error] reporter/release
This looks very similar to another SO post. I tried adding releaseSettings to build.sbt as suggested but it throws error
build.sbt:62: error: not found: value releaseSettings
I tried import sbtrelease.Release._ but that throws
error: object Release is not a member of package sbtrelease
At this point I feel the solution mentioned is no longer valid. Also, I don't see any reference to releaseSettings in sbt-release readme. Any idea how to get this working?
sbt.version = 0.13.15 && sbt-release: "1.0.6"
The release settings should be only in your root project. So, in order to have that:
First, your build.sbt could be something like this
lazy val root: Project = project.in(file("."))
.settings(Releases.settings: _*)
.aggregate(module1, module2)
Then, your project/Releases.scala like:
object Releases {
// You need to custom these to reflect your actual procedure
private val releaseProcess = Def.setting {
Seq[ReleaseStep](
checkSnapshotDependencies,
inquireVersions,
runClean,
runTest,
setReleaseVersion,
commitReleaseVersion,
tagRelease,
publishArtifacts,
setNextVersion,
commitNextVersion,
pushChanges
)
}
val settings = Seq(
releaseCommitMessage := s"Set version to ${(version in ThisBuild).value}",
releaseTagName := (version in ThisBuild).value,
releaseProcess := releaseProcess.value
)
}
By default you need a version.sbt with your current version. Lets say:
version in ThisBuild := "1.0.0-SNAPSHOT"
Then just:
sbt release

How to avoid activator executing compile task twice upon accessing Play page?

I am trying to run a custom task before compilation of a Play 2.3 application. I have this in my build.sbt file:
lazy val helloTask = TaskKey[Unit]("hello", "hello")
helloTask := {
println("hello test")
}
(compile in Compile) <<= (compile in Compile) dependsOn helloTask
When I run activator ~run and then open a page in the browser, I get the following output:
C:\Development\test>activator ~run
[info] Loading project definition from C:\Development\test\project
[info] Set current project to play (in build file:/C:/Development/test/)
--- (Running the application from SBT, auto-reloading is enabled) ---
[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)
hello test
[success] Compiled in 418ms
hello test
hello test
[info] play - Application started (Dev)
It seems my custom task is running three times. Is there a way I can avoid this?
I had the same problem and I found solution.
In Sbt you have three Scopes by configuration axis :
Compile which defines the main build (src/main/scala).
Test which defines how to build tests (src/test/scala).
Runtime which defines the classpath for the run task.
You must use Runtime instead of Compile. It should looks like this:
lazy val helloTask = taskKey[Unit]("hello")
helloTask := println("hello test")
(compile in Runtime) <<= (compile in Runtime) dependsOn helloTask
This was the first result on google so I would like to post my current solution to the problem which actually works with play 2.8 and a multi project build. It is slightly modified. The proposed solution by #bartholomaios results in a compile loop for me.
lazy val helloTask = taskKey[Unit]("hello")
helloTask := println("hello test")
lazy val module1: Project = (project in file("modules/module1"))
# Run a task before sbt module1/run
((module1 / run) in Compile) := (((module1 / run) in Compile) dependsOn Compile / helloTask).evaluated
# Run a task before sbt module1/docker:stage
((module1 / stage) in Docker) := (((module1 / stage) in Docker) dependsOn Compile / helloTask).value

What does extend for a configuration do?

In SBT I create a new config, called katebush, as follows:
lazy val KateBush: Configuration = config("katebush")
When I try to run katebush:compile I get an error. That's what I expect.
> katebush:compile
[error] No such setting/task
[error] katebush:compile
[error] ^
Now I extend Compile in my config definition, and I expect to pick up the compile from the inherited scope.
lazy val KateBush: Configuration = config("katebush") extend Compile
Except it doesn't work:
> katebush:compile
[error] No such setting/task
[error] katebush:compile
[error] ^
But if I add in the defaults to the config (in build.sbt) so it looks as follows:
lazy val KateBush: Configuration = config("katebush") extend Compile
inConfig(KateBush)(Defaults.compileSettings)
it works fine:
> katebush:compile
[info] Updating {file:/Users/jacek/sandbox/so-25596360/}so-25596360...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[success] Total time: 0 s, completed Aug 31, 2014 11:35:47 PM
So, my question is, what exactly does extend for a configuration do?
DISCLAIMER I've got a rather basic understanding of the config concept of sbt.
tl;dr Extending a configuration is solely to inherit the dependencies groups not settings.
From the sources of final case class Configuration:
def extend(configs: Configuration*) = Configuration(name, description, isPublic, configs.toList ::: extendsConfigs, transitive)
By default, extendsConfigs is Nil as can be seen in the sbt.Configurations object:
def config(name: String) = new Configuration(name)
that resolves to (note Nil)
def this(name: String) = this(name, "", true, Nil, true)
In sbt.IvySbt.toIvyConfiguration:
import org.apache.ivy.core.module.descriptor.{ Configuration => IvyConfig }
and that's where the support of the config concept ends in sbt and Ivy steps in. That's where you'd have to look at the documentation of Ivy.
But before that read Advanced configurations example where it says:
This is an example .scala build definition that demonstrates using Ivy
configurations to group dependencies.
That's the beginning of the explanation. Ivy configurations are to group dependencies and extending a configuration is to extend the grouping.
From the official documentation of Ivy about the conf element:
a configuration is a way to use or construct a module.(...)
a module may need some other modules and artifacts only at build time, and some others at runtime. All those differents ways to use or build a module are called in Ivy module configurations.
Reading along you can find the answer to your question (that I'm myself yet to digest, too):
A configuration can also extend one or several other ones of the same
module. When a configuration extends another one, then all artifacts
required in the extended configuration will also be required in the
configuration that extends the other one. For instance, if
configuration B extends configuration A, and if artifacts art1 and
art2 are required in configuration A, then they will be automatically
required in configuration B. On the other hand, artifacts required in
configuration B are not necessarily required in configuration A.
This notion is very helpful to define configurations which are similar
with some differences.
At the bottom of the page, there's the Examples section with an example with runtime config that has "runtime will be composed of all dependencies, all transitively, including the dependencies declared only in compile."
With this, you can now understand the config concept in sbt as a dependencies groups and what's grouped in Compile is available in Runtime as its definition looks as follows:
lazy val Runtime = config("runtime") extend (Compile)
I have just had to figure this out, so I thought this was worth clarifying. The configuration has to be added to the project for delegation to the extended configuration to occur:
lazy val KateBush: Configuration = config("katebush") extend Compile
lazy val root = (project in file(".")).configs(KateBush)
will work fine. If you
inspect katebush:compile
then you can view the delegation chain:
...
[info] Delegates:
[info] katebush:compile
[info] compile:compile
[info] *:compile
[info] {.}/katebush:compile
[info] {.}/compile:compile
[info] {.}/*:compile
[info] */katebush:compile
[info] */compile:compile
[info] */*:compile
...

Specifying rootProject in build.sbt

In my Build.scala, I have:
override def rootProject = Some(frontendProject)
I'm trying to convert to the newer build.sbt format, but don't know the equivalent of this line. How do I set the project for sbt to load by default when using build.sbt?
I'm still not sure that I understood you right, but you said about multi-project build, so I assume that you want to define a root project which aggregates subprojects. Here is how you can do that (in your root build.sbt):
lazy val root = project.in( file(".") ).aggregate(subProject1, subProject2)
lazy val subProject1 = project in file("subProject1")
lazy val subProject2 = project in file("subProject2")
See sbt documentation about multi-projects.
Then if you want to set the default project to load on sbt startup to a sub-project, in addition to your answer to this SO question, I can suggest
run sbt with sbt "project XXX" shell command
or adding this line to your build.sbt:
onLoad in Global := { Command.process("project XXX", _: State) } compose (onLoad in Global).value
In both cases sbt first loads the root project and then the subproject.
I've found the following script to be helpful:
#!/usr/bin/env bash
exec "sbt" "project mysubproject" "shell"
exit $?

Can sbt execute "compile test:compile it:compile" as a single command, say "*:compile"?

I'm running compile test:compile it:compile quite often and...would like to cut the number of keystrokes to something like *:compile. It doesn't seem to work, though.
$ sbt *:compile
[info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
[info] Loading project definition from /Users/jacek/oss/scalania/project
[info] Set current project to scalania (in build file:/Users/jacek/oss/scalania/)
[error] No such setting/task
[error] *:compile
[error] ^
Is it possible at all? I use SBT 0.13.
test:compile implies a compile so compile doesn't need to be explicitly run before test:compile. If your IntegrationTest configuration extends Test, it:compile implies test:compile.
One option is to define an alias that executes multiple commands:
sbt> alias compileAll = ; test:compile ; it:compile
See help alias and help ; for details. You can make this a part of your build with:
addCommandAlias("compileAll", "; test:compile ; it:compile")
The other option is to define a custom task that depends on the others and call that:
lazy val compileAll = taskKey[Unit]("Compiles sources in all configurations.")
compileAll := {
val a = (compile in Test).value
val b = (compile in IntegrationTest).value
()
}

Resources