SBT won't autocomplet on keys defined in globalSettings of an AutoPlugin - sbt

object BlaBlaPlugin extends AutoPlugin {
object autoImport {
lazy val blabla = taskKey[Unit]("")
}
import autoImport._
override lazy val globalSettings = Seq(
blabla := println("Hello")
)
}
Gives:
> bla
{invalid input}
> blabla
Hello
[success] Total time: 0 s, completed 22-Dec-2015 00:33:21
Why wouldn't the autocompletion work?

To clarify,
> bla[tab]
{invalid input}
where [tab] is actually a key stroke.
This might be a bug and/or limitation of sbt. Please file this on Github issue.

Related

Best place to declare own settings so I can access them from sbt shell and my .sbt and .scala build files?

My settings are declared in project/Utils.scala in an object BuildSupport and are made available in .sbt and .scala files with import BuildSupport._.
However, this doesn't work for sbt shell as seen in above screenshot since I can't import them there. For completeness sake, I have tried eval import BuildSupport._ but all that got me was <eval>:1: error: illegal start of simple expression.
How do I have to define own settings and tasks so I can access them from:
project/*.scala,
*.sbt and
sbt shell?
I assume defining them in an AutoPlugin would work, but I'd rather not have to go to that length. Is there any other way?
Declaring an adhoc AutoPlugin (https://www.scala-sbt.org/1.x/docs/Plugins.html) is the way to go.
Auto plugins ...
can define autoImport object, which is automatically included into build.sbt. This can be used for keys.
can define globalSettings, buildSettings, or projectSettings to inject settings and tasks either at the build-level or at the subproject level. This should become available in sbt shell.
A plugin doesn't have to be a published on its own. You can declare one inside project/*.scala like any other Scala object.
The plugin in the linked documentation is great for copy-pasting:
package sbthello
import sbt._
import Keys._
object HelloPlugin extends AutoPlugin {
override def trigger = allRequirements
object autoImport {
val helloGreeting = settingKey[String]("greeting")
val hello = taskKey[Unit]("say hello")
}
import autoImport._
override lazy val globalSettings: Seq[Setting[_]] = Seq(
helloGreeting := "hi",
)
override lazy val projectSettings: Seq[Setting[_]] = Seq(
hello := {
val s = streams.value
val g = helloGreeting.value
s.log.info(g)
}
)
}

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)
)
}

How to obtain the value of scalacOptions for the meta-project in a plugin?

In the ensime-sbt plugin, we need to be able to obtain the compiler flags that the sbt process is using to compile the build definition (i.e. everything under project).
We have the State object, but I can't see any way to get the compiler flags, where are they?
Note: this is not the compile flags for the projects themselves, I mean only for the build definition.
e.g. say the project has this in the project/plugins.sbt
scalacOptions += "-Xfuture"
how can we read that from the plugin?
This is somewhat related to How to share version values between project/plugins.sbt and project/Build.scala?
You can generate a build file for the project. And, to do that, you have to add the plugin in both the meta-project and the meta-project for the meta-project.
import sbt._
import Keys._
object MyPlugin extends AutoPlugin {
object autoImport {
val scalacOptions4Meta = SettingKey[Seq[String]]("scalacOptions4Meta")
val mygenerator = TaskKey[Seq[File]]("mygenerator")
}
import autoImport._
override def trigger = allRequirements
override lazy val projectSettings = Seq(
mygenerator := {
val file = sourceManaged.value / "settings4Meta.scala"
val opts = (scalacOptions in Compile).value
.map(opt => "\"" + opt + "\"")
val content = s"""
import sbt._
import Keys._
object MyBuild extends Build {
lazy val root = Project("root", file("."))
.settings(
MyPlugin.autoImport.scalacOptions4Meta := Seq(${opts.mkString(",")})
)
}"""
IO.write(file, content)
Seq(file)
}
)
}
project/plugins.sbt:
addSbtPlugin("myplugin" % "myplugin" % "0.1-SNAPSHOT")
scalacOptions := Seq("-Xfuture")
sourceGenerators in Compile += mygenerator.taskValue
project/project/plugins.sbt:
addSbtPlugin("myplugin" % "myplugin" % "0.1-SNAPSHOT")

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

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
)
)
}

Resources