How to make "test" classes available in the "it" (integration test) configuration? - sbt

I would like to share a helper trait between my "test" and "it" configurations in SBT, but I have not figured out how.
Here is a minimal example:
project/Build.scala
import sbt._
import Keys._
object MyBuild extends Build {
val scalaTest = "org.scalatest" %% "scalatest" % "2.0" % "test,it"
lazy val myProject =
Project(id = "my-project", base = file("."))
.configs(IntegrationTest)
.settings(Defaults.itSettings: _*)
.settings(
scalaVersion := "2.10.3",
libraryDependencies ++= Seq(
scalaTest
)
)
}
src/test/scala/Helpers.scala
trait Helper {
def help() { println("helping.") }
}
src/test/scala/TestSuite.scala
import org.scalatest._
class TestSuite extends FlatSpec with Matchers with Helper {
"My code" should "work" in {
help()
true should be(true)
}
}
src/it/scala/ItSuite.scala
import org.scalatest._
class ItSuite extends FlatSpec with Matchers with Helper {
"My code" should "work" in {
help()
true should be(true)
}
}
then, in sbt, "test" works:
sbt> test
helping.
[info] TestSuite:
[info] My code
[info] - should work
[info] Run completed in 223 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 0 s, completed Dec 17, 2013 1:54:56 AM
but "it:test" doesn't compile:
sbt> it:test
[info] Compiling 1 Scala source to ./target/scala-2.10/it-classes...
[error] ./src/it/scala/ItSuite.scala:3: not found: type Helper
[error] class ItSuite extends FlatSpec with Matchers with Helper {
[error] ^
[error] ./src/it/scala/ItSuite.scala:5: not found: value help
[error] help()
[error] ^
[error] two errors found
[error] (it:compile) Compilation failed
[error] Total time: 1 s, completed Dec 17, 2013 1:55:00 AM

If you want to share code from Test configuration, it's probably better to create a custom test configuration from Test. See Custom test configuration.
Your project/Build.scala becomes:
import sbt._
import Keys._
object MyBuild extends Build {
lazy val FunTest = config("fun") extend(Test)
val scalaTest = "org.scalatest" %% "scalatest" % "2.0" % "test"
lazy val myProject =
Project(id = "my-project", base = file("."))
.configs(FunTest)
.settings(inConfig(FunTest)(Defaults.testSettings) : _*)
.settings(
scalaVersion := "2.10.3",
libraryDependencies ++= Seq(
scalaTest
)
)
}
Also rename src/it/ to src/fun/. Now fun:test works:
> fun:test
helping.
[info] ItSuite:
[info] My code
[info] - should work
[info] Run completed in 245 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 1 s, completed Dec 17, 2013 8:43:17 AM

You can redefine the IntegrationTest Configuration in your project to extend the Test configuration instead of the Runtime Configuration (the default). This will make everything in your test configuration available to your IntegrationTest configuration.
import sbt._
import Keys._
object MyBuild extends Build {
val scalaTest = "org.scalatest" %% "scalatest" % "2.0" % "test,it"
lazy val IntegrationTest = config("it") extend(Test)
lazy val myProject =
Project(id = "my-project", base = file("."))
.configs(IntegrationTest)
.settings(Defaults.itSettings: _*)
.settings(
scalaVersion := "2.10.3",
libraryDependencies ++= Seq(
scalaTest
)
)
}

Related

How to pass arguments to InputTask without entering sbt interactive mode?

With the following sample SBT build file, I can pass arguments to my InputTask from within the SBT Interactive Mode but not from without. Is there a way?
Sample build.sbt:
import complete.DefaultParsers._
lazy val sampleDoSomething = inputKey[Unit]("Will print arguments.")
lazy val commonSettings = Seq(
organization := "com.example",
version := "0.1.0-SNAPSHOT"
)
lazy val taskInputTaskProject = (project in file(".")).
settings(commonSettings: _*).
settings(
sampleDoSomething := {
println("Arguments: ")
val args = spaceDelimited("<arg>").parsed
args foreach println
}
)
Successfully invoking task from within SBT Interactive mode:
$ sbt
[info] Set current project to taskInputTaskProject (in build file:/study/sbt/input-tasks/)
> sampleDoSomething a b c
Arguments:
a
b
c
[success] Total time: 0 s, completed Mar 22, 2016 1:06:58 PM
Successfully Invoking task from command line without arguments:
$ sbt sampleDoSomething
[info] Set current project to taskInputTaskProject (in build file:/study/sbt/input-tasks/)
Arguments:
[success] Total time: 0 s, completed Mar 22, 2016 1:06:18 PM
Failure to invoke task from command line with arguments:
$ sbt sampleDoSomething a b c
[info] Set current project to taskInputTaskProject (in build file:/study/sbt/input-tasks/)
Arguments:
[success] Total time: 0 s, completed Mar 22, 2016 1:06:44 PM
[error] Not a valid command: a
[error] Expected 'all'
[error] Not a valid project ID: a
[error] Expected ':' (if selecting a configuration)
[error] Not a valid key: a
[error] a
[error] ^
sbt "sampleDoSomething a b c"
See doc: http://www.scala-sbt.org/0.13/docs/Running.html#Batch+mode
Cheers

SBT Assembly Plugin Error

I'm trying to run sbt assembly on my project but I get error saying:
[error] Not a valid command: assembly
[error] Not a valid project ID: assembly
[error] Expected ':' (if selecting a configuration)
[error] Not a valid key: assembly
[error] assembly
[error] ^
I have the following structure:
MyProject
- project
- assembly.sbt
- build.properties
- BuildSettings.scala
- MyProjectBuild.scala
- src
- main
- com
- mypkg
- MyMainClass.scala
I have the following in my assembly.sbt:
resolvers += Resolver.url("artifactory", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns)
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
// dont upgrade to 0.12.0 as there is assembly conflict
My build.properties is:
sbt.version=0.13.6
My BuildSettings.scala is:
import sbt._
import Keys._
object BuildSettings {
lazy val basicSettings = Seq[Setting[_]](
organization := "com.eon.vpp",
version := "0.1.0-SNAPAHOT",
description := "vpp metrics producer to a kafka instance",
scalaVersion := "2.11.7",
scalacOptions := Seq("-deprecation", "-encoding", "utf8"),
resolvers ++= Dependencies.resolutionRepos
)
// sbt-assembly settings for building one fat jar
import sbtassembly.Plugin._
import AssemblyKeys._
lazy val sbtAssemblySettings = assemblySettings ++ Seq(
jarName in assembly := {
name.value + "-" + version.value + ".jar"
},
// META-INF discarding
mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
{
case PathList("META-INF", xs # _*) => MergeStrategy.discard
case x => MergeStrategy.first
}
}
)
lazy val buildSettings = basicSettings ++ sbtAssemblySettings
}
Any suggestions as to why is this error?
Yes, I figured out what the problem was. I had to move the plugins.sbt file inside the project folder. It was that simple!

How to read the modified setting defined in my own sbt plugin

I tried to create my own sbt plugin by following this guide, and had the following code:
build.sbt in myplugin
sbtPlugin := true
lazy val plugin = (project in file(".")).
settings(
name := "myplugin",
version := "0.1-SNAPSHOT",
scalaVersion := "2.10.4"
)
HelloPlugin.scala in myplugin
package sbthello
import sbt._
import Keys._
object HelloPlugin extends AutoPlugin {
object autoImport {
val greeting = settingKey[String]("greeting")
val obfuscate = taskKey[String]("Obfuscates files.")
}
import autoImport._
lazy val baseSettings: Seq[Def.Setting[_]] = Seq(
greeting := "Hi!",
obfuscate := {
println(greeting.value)
greeting.value + " value"
}
)
override val projectSettings =
inConfig(Compile)(baseSettings)
}
the build.sbt in test project is
lazy val usage = (project in file("."))
.enablePlugins(HelloPlugin).
settings(
name := "sbt-test",
version := "0.1",
scalaVersion := "2.10.4")
.settings(
greeting := "Hello"
)
with the files above, the execution result is:
> show obfuscate
Hi!
[info] Hi! value
> show greeting
[info] Hello
the obfuscate task cannot read the "Hello" value
if modify greeting := "Hi!" in HelloPlugin.scala to greeting in obfuscate := "Hi!",
the obfuscate task can read the "Hello" value now
> show greeting
[info] Hello
> show obfuscate
Hello
[info] Hello value
but now I cannot remove greeting := "Hello" in buildInDemo.sbt, or else it will have such errors:
[error] References to undefined settings:
[error]
[error] compile:greeting from compile:obfuscate ((sbthello.HelloPlugin) HelloPlugin.scala:40)
[error] Did you mean compile:obfuscate::greeting ?
[error]
[error] compile:greeting from compile:obfuscate ((sbthello.HelloPlugin) HelloPlugin.scala:40)
[error] Did you mean compile:obfuscate::greeting ?
[error]
configuration scoping
override val projectSettings =
inConfig(Compile)(baseSettings)
This scopes all the settings in baseSettings into Compile configuration. So it's the same as saying:
Define greeting in Compile setting.
Refer to the greeting in Compile setting from obfuscate in Compile task.
So in build.sbt you should customize as
greeting in Compile := "Hello"
You can access this setting from the shell as:
> compile:greeting
If you don't want the behavior, don't put baseSettings in inConfig(...).
task and configuration scoping
greeting in obfuscate is task scoping. So combined with inConfig(...) that'll require
greeting in (Compile, obfuscate) := "Hello"
to customize.
For more details see Scopes.

ivyPaths in globalSettings for a plugin

object BlaBlaPlugin extends AutoPlugin {
object autoImport {
lazy val blabla = settingKey[Unit]("")
}
import autoImport._
override lazy val globalSettings = Seq(
blabla := println(ivyPaths.value.ivyHome.get.getPath)
)
}
I get:
[error] Reference to undefined setting:
[error]
[error] */*:ivyPaths from */*:blabla ((BlaBlaPlugin) BlaBlaPlugin.scala:11)
Isn't ivyPaths defined at a Global scope?
I can access it in .sbt/global.sbt but not in the globalSettings of a plugin.
Cheers
There are several issues with the plugin.
projectSettings
Isn't ivyPaths defined at a Global scope?
No it's not.
> inspect ivyPaths
[info] Setting: sbt.IvyPaths = sbt.IvyPaths#13e5338f
[info] Description:
[info] Configures paths used by Ivy for dependency management.
[info] Provided by:
[info] {file:/Users/xxx/foo/}root/*:ivyPaths
[info] Defined at:
[info] (sbt.Classpaths) Defaults.scala:1128
[info] Dependencies:
[info] *:appConfiguration
[info] *:baseDirectory
[info] Reverse dependencies:
[info] *:cleanCacheIvyDirectory
[info] *:ivyConfiguration
[info] *:blabla
[info] Delegates:
[info] *:ivyPaths
[info] {.}/*:ivyPaths
[info] */*:ivyPaths
This shows that ivyPaths is in the format of {file:/Users/xxx/foo/}root/*:ivyPaths, which is scoped to the current project root. If you're in the mood for Star Wars coding (use the source, Luke) Defaults.scala is usually a good place to start.
So the first thing to change would be to use projectSettings.
override requires
Also note that sbt's builtin settings and tasks are loaded using auto plugins, so if you're depending on them, you need to make sure your plugins are placed after them. To do that you need to override requires method as follows:
override def requires: Plugins = sbt.plugins.IvyPlugin
putting it all together
import sbt._
import Keys._
object BlaBlaPlugin extends AutoPlugin {
override def requires: Plugins = sbt.plugins.IvyPlugin
object autoImport {
lazy val blabla = settingKey[Unit]("")
}
import autoImport._
override lazy val projectSettings = Seq(
blabla := println(ivyPaths.value.ivyHome.get.getPath)
)
}

Why does publishing plugin project fail with RuntimeException: Repository for publishing is not specified?

I am trying to publish an SBT plugin to a repository. I'm not sure if this has any relevance, but our plugin loads the sbt-twirl plugin - Googling around, it seems like publishConfiguration might be overriden:
new PublishConfiguration(None, "dotM2", arts, Seq(), level)
When I run the publish task, artifacts are deployed to the repo, but the sbt task then fails:
sbt (my-sbt-plugin)> publish
[info] Loading global plugins from ...
...
[info] Done packaging.
[info] published sbt-my-sbt-plugin to http://my.repo.com/.../sbt-my-sbt-plugin-0.1-SNAPSHOT.jar
java.lang.RuntimeException: Repository for publishing is not specified.
.... stack trace here ....
[error] (my-sbt-plugin/*:publishConfiguration) Repository for publishing is not specified.
What is causing the error, and what could I do to stop the publishing from failing?
** Update ** Here is inspect publish
sbt (my-sbt-plugin)> inspect publish
[info] Task: Unit
[info] Description:
[info] Publishes artifacts to a repository.
[info] Provided by:
[info] {file:/path/to/my-sbt-plugin/}my-sbt-plugin/*:publish
[info] Defined at:
[info] (sbt.Classpaths) Defaults.scala:988
[info] Dependencies:
[info] my-sbt-plugin/*:ivyModule
[info] my-sbt-plugin/*:publishConfiguration
[info] my-sbt-plugin/*:publish::streams
[info] Delegates:
[info] my-sbt-plugin/*:publish
[info] {.}/*:publish
[info] */*:publish
[info] Related:
[info] plugin/*:publish
Here's how I've configured publishing (with some of the plugin settings, excluding libraryDependencies and 1 or 2 other settings)
lazy val plugin = project
.settings(publishSbtPlugin: _*)
.settings(
name := "my-sbt-plugin",
sbtPlugin := true,
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
)
def publishSbtPlugin = Seq(
publishMavenStyle := true,
publishTo := {
val myrepo = "http://myrepo.tld/"
if (isSnapshot.value) Some("The Realm" at myrepo + "snapshots")
else Some("The Realm" at myrepo + "releases")
},
credentials += Credentials(Path.userHome / ".ivy2" / ".credentials")
)
tl;dr Don't use lazy val plugin = project to define a project (for unknown yet reasons)
After few comments it turned out that the issue was that the name of the project plugin as defined using lazy val plugin = project. It seems that the name is somehow reserved. Change the project's name to any other name than plugin and start over.
Specifying a project name other than "plugin" resolved the issue. I simplified the build definition a bit by removing a redundant build.sbt in 1 of the projects and am just using a full build definition in project directory. The root project that hosts the multi-project build is also reconfigured for no publishing:
lazy val root =
Project("sbt-my-plugin-root", file("."))
.settings(noPublishing: _*)
.aggregate(sbtMyPluginModule)
lazy val sbtMyPluginModule =
Project("sbt-my-plugin-module", file("sbt-my-plugin-module"))
.settings(publishSbtPlugin: _*)
.settings(
name := "sbt-my-plugin-module",
organization := "com.my.org",
sbtPlugin := true
)
lazy val noPublishing = seq(
publish := (),
publishLocal := ()
)
lazy val publishSbtPlugin = Seq(
publishMavenStyle := true,
publishArtifact in Test := false,
publishTo := {
val myrepo = "http://myrepo.tld/"
if (isSnapshot.value) Some("The Realm" at myrepo + "snapshots")
else Some("The Realm" at myrepo + "releases")
},
credentials += Credentials(Path.userHome / ".ivy2" / ".credentials")
)
if you trying this on your local then use publishLocal (not publish) as follows:
sbt clean compile publish-local

Resources