Setting cannot depend on a task when adding logging to setting key - scala

I am updating an sbt plugin which has a SettingKey from the fm-sbt-s3-resolver. I have made some progress on explicitly adding the needed setting as a side effect of the question here:
Logging from an sbt plugin
object MyPlugin {
override def requires = S3ResolverPlugin
override def trigger = allRequirements
override lazy val globalSettings = Seq(
resolvers ++= repos,
publishMavenStyle := true,
S3ResolverPlugin.autoImport.s3CredentialsProvider := {bucket: String =>
new AWSCredentialsProviderChain(
new EnvironmentVariableCredentialsProvider(),
PropertyFilesCredentialProvider.create(bucket, streams.value.log)
)
}
)
}
When I try to add logging using streams.value.log, sbt throws an error:
[error] A setting cannot depend on a task
[error] PropertyFilesCredentialProvider.create(bucket, streams.value.log)

In this case, you need to depend on setting sLog instead of task streams.
import sbt._
import sbt.Keys._
object Logs extends AutoPlugin {
object autoImport {
val settingLog = settingKey[Unit]("Uses setting sLog")
val taskLog = taskKey[Unit]("Uses task streams log")
}
import autoImport._
override def trigger = allRequirements
override def projectSettings: Seq[Def.Setting[_]] = Seq(
settingLog := { sLog.value.info("Logging on settings execution") },
taskLog := { streams.value.log.info("Logging on task execution") }
)
}
The difference with settingLog is the it will log when sbt boots.
~/w/t/stackoverflow $ sbt
[info] Logging on settings execution
Repeated calls to settingLog will not produce any logging anymore. But taskLog will print a log every time you call it

Related

sbt autoplugin: add javaagent for task

I have a sbt autoplugin and when the user runs a task I want to fork a new JVM with a -javaagent. The task should measure memory using jamm.
object SbtMemory extends AutoPlugin {
object autoImport {
val agentTest = inputKey[Unit]("Run task with javaagent")
}
def makeAgentOptions(classpath: Classpath) : String = {
val jammJar = classpath.map(_.data).filter(_.toString.contains("jamm")).head
s"-javaagent:$jammJar"
}
override lazy val projectSettings =
Seq(
agentTest := agentTask.value,
fork in agentTest := true,
javaOptions in agentTest += (dependencyClasspath in Test).map(makeAgentOptions).value
)
lazy val agentTask = Def.task {
val o = new Array[Byte](1024*1024)
val mm = new MemoryMeter()
println("Size of new Array[Byte](1024*1024): " + mm.measureDeep(o))
}
}
When I run sbt perf from the command line, I get the following exception:
java.lang.IllegalStateException: Instrumentation is not set; Jamm must be set as -javaagent
I also tried printing the javaOptions and the -javaagent option was not set.
How can I add the -javaagent javaOption inside the plugin to run the task with jamm?
Thanks!
Apparently, fork is only available for the run and test task. I added my own forking code and moved the measure code to a separate class MemoryMeasure:
val mainClass: String = "MemoryMeasure"
val forkOptions = ForkOptions(
bootJars = (fullClasspath in Test).value.files,
runJVMOptions = Seq(
(dependencyClasspath in Test).map(makeAgentOptions).value
)
)
val process = Fork.java.fork(forkOptions, mainClass +: arguments)
def cancel() = {
process.destroy()
1
}
val exitCode = try process.exitValue() catch { case e: InterruptedException => cancel() }

SBT 0.13 Build.scala References to undefined settings

I am new to SBT and I have been trying to build a custom task for this build.
I have a simple build project:
import sbt._
import Keys._
object JsonBuild extends Build{
lazy val barTask = taskKey[Unit]("some simple task")
val afterTestTask1 = barTask := { println("tests ran!") }
val afterTestTask2 = barTask <<= barTask.dependsOn(test in Test)
lazy val myBarTask = taskKey[Unit]("some simple task")
//val afterMyBarTask1 = myBarTask := { println("tests ran!") }
lazy val afterMyBarTask2 = myBarTask <<= (myBarTask).dependsOn(test in Test) map { _ => println("tests ran!") }
//settings ++ Seq(afterMyBarTask2)
override lazy val settings = super.settings ++ Seq(afterMyBarTask2)
}
I keep getting the error:
References to undefined settings:
{.}/*:myBarTask from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
{.}/test:test from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
Did you mean test:test ?
I have googled around and I cannot find a solution.
Can you explain why it is not working?
lazy val myBarTask = taskKey[Unit]("some simple task")
override lazy val settings = super.settings ++ Seq(myBarTask := { (test in Test).value; println("tests ran!") } )
myBarTask is undefined when you call dependsOn. you should define it before using dependsOn. also value call on key (task/setting) is now preferred way to depend on other keys. you can still use your version, but define myBarTask
This has been bothering.
I did a bit more reading.
I think I know why the above code does not work.
lazy val afterMyBarTask2 = myBarTask <<= (myBarTask).dependsOn(test in Test) map { _ => println("tests ran!") }
When I write (myBarTask).dependsOn(test in Test), the project scope for test is chosen by SBT as ThisBuild.
{.}/test:test from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
ThisBuild project scope does not have the setting test in configuration Test.
Only projects have the setting test.
The key I think that setting is added by some default SBT plugin to the projects settings.
You check what scopes settings exist in SBT by using the inspect command.
If you type in the SBT REPL:
{.}/test:test
The output is:
inspect {.}/test:test
[info] No entry for key.
SBT correctly suggests:
test:test which is:
{file:/C:/Users/haques/Documents/workspace/SBT/jsonParser/}jsonparser/test:test
If the project is not specified in the project scope axis, SBT chooses the current project by default.
Every SBT project if not specified has its own project settings.

combining input task with dynamic task in sbt

I'd like to make an input task that proceed user input and generate bunch of subtasks to run. Here is the example:
import sbt._
import Keys._
import Def.Initialize
import complete.DefaultParsers._
object TestBuild extends Build {
val sampleInput = inputKey[Seq[String]]("sample dynamic input task")
val sampleDynamic = taskKey[Seq[String]]("sample dynamic task")
override lazy val settings = super.settings ++ Seq(
sampleDynamic := Def.taskDyn {
val sources = Seq("ab", "csd", "efda")
sources.map(sampleTaskFor _).joinWith(_.join)
}.value,
sampleInput := Def.inputTaskDyn {
val sources = spaceDelimited("<arg>").parsed
sources.map(sampleTaskFor _).joinWith(_.join)
}.value
)
private def sampleTaskFor(source : String) : Initialize[Task[String]] = Def.task {
source + " : " + source
}
}
There are two samples. The first works and shows simple taskDyn with predefined input. The second is intended dynamic task with user input that refuses to compile with error that I can not interpret
[error] home/project/build.scala:15: Illegal dynamic reference: Def
[error] sampleInput := Def.inputTaskDyn {
[error] ^
[error] one error found
[error] (compile:compile) Compilation failed
How can I avoid it?
Trial and error log
There I would append my question with different proposed changes that still can not solve the issue
replace InputTask.value with InputTask.evaluated
sources.map(sampleTaskFor _).joinWith(_.join)
- }.value
+ }.evaluated
)
If I would able to define correct InputTask it should be accessed via evaluated method, as I've found both in documentation and in practice after trying to play with different InputTasks that compiles.
Still that would not fix the issue that sbt macro engine refuses provided inputTaskDyn.
waiting for other suggestions
Trial and error method gave me the answer but non a single bit of understanding. Here it is:
import sbt._
import Keys._
import Def.Initialize
import complete.DefaultParsers._
object TestBuild extends Build {
val sampleInput = inputKey[Seq[String]]("sample dynamic input task")
val sampleDynamic = taskKey[Seq[String]]("sample dynamic task")
override lazy val settings = super.settings ++ Seq(
sampleDynamic := Def.taskDyn {
val sources = Seq("ab", "csd", "efda")
sources.map(sampleTaskFor _).joinWith(_.join)
}.value,
sampleInput := Def.inputTaskDyn {
val sources = spaceDelimited("<arg>").parsed
sampleTaskAll(sources)
}.evaluated
)
private def sampleTaskFor(source : String) : Initialize[Task[String]] = Def.task {
source + " : " + source
}
private def sampleTaskAll(sources : Seq[String]) : Initialize[Task[Seq[String]]] = Def.taskDyn {
sources.map(sampleTaskFor _).joinWith(_.join)
}
}
For a reason I can not comprehend you should isolate creating multitask out of sequence of single tasks in a separate method.

SBT plugin - User defined configuration for Command via their build.sbt

I'm writing an SBT Plugin that adds a Command and would like users to be able to configure this Command by setting variables in their build.sbt. What is the simplest way to achieve this?
Here is an simplified example of what the Plugin looks like:
import sbt.Keys._
import sbt._
object MyPlugin extends Plugin {
override lazy val settings = Seq(commands += Command.args("mycommand", "myarg")(myCommand))
def myCommand = (state: State, args: Seq[String]) => {
//Logic for command...
state
}
}
I would like someone to be able to add the follow to their build.sbt file:
newSetting := "light"
How do I make this available as a String variable from inside the myCommand Command above?
Take a look at the example here: http://www.scala-sbt.org/release/docs/Extending/Plugins.html#example-plugin
In this example, a task and setting are defined:
val newTask = TaskKey[Unit]("new-task")
val newSetting = SettingKey[String]("new-setting")
val newSettings = Seq(
newSetting := "test",
newTask <<= newSetting map { str => println(str) }
)
A user of your plugin could then provide their own value for the newSetting setting in their build.sbt:
newSetting := "light"
EDIT
Here's another example, closer to what you're going for:
Build.scala:
import sbt._
import Keys._
object HelloBuild extends Build {
val newSetting = SettingKey[String]("new-setting", "a new setting!")
val myTask = TaskKey[State]("my-task")
val mySettings = Seq(
newSetting := "default",
myTask <<= (state, newSetting) map { (state, newSetting) =>
println("newSetting: " + newSetting)
state
}
)
lazy val root =
Project(id = "hello",
base = file("."),
settings = Project.defaultSettings ++ mySettings)
}
With this configuration, you can run my-task at the sbt prompt, and you'll see newSetting: default printed to the console.
You can override this setting in build.sbt:
newSetting := "modified"
Now, when you run my-task at the sbt prompt, you'll see newSetting: modified printed to the console.
EDIT 2
Here's a stand-alone version of the example above: https://earldouglas.com/ext/stackoverflow.com/questions/17038663/
I've accepted #James's answer as it really helped me out. I moved away from using a Commands in favour of a Task (see this mailing list thread). In the end my plugin looked something like this:
package packge.to.my.plugin
import sbt.Keys._
import sbt._
object MyPlugin extends Plugin {
import MyKeys._
object MyKeys {
val myTask = TaskKey[Unit]("runme", "This means you can run 'runme' in the SBT console")
val newSetting = SettingKey[String]("newSetting")
}
override lazy val settings = Seq (
newSetting := "light",
myTask <<= (state, newSetting) map myCommand
)
def myCommand(state: State, newSetting: String) {
//This code runs when the user types the "runme" command in the SBT console
//newSetting is "light" here unless the user overrides in their build.sbt (see below)
state.log.info(newSetting)
}
}
To override the newSetting in the build.sbt of a project that uses this plugin:
import packge.to.my.plugin.MyKeys._
newSetting := "Something else"
The missing import statement had me stuck for a while!

Getting an SBT task to depend on the OneJar task

I'm having trouble getting my new SBT task 'install' to depend on the OneJar task. Here's my Build.scala file:
import sbt._
import Keys._
import com.github.retronym.SbtOneJar._
object BuildBroBuild extends Build {
val install = TaskKey[Unit]("install", "Installs the JAR and a launcher script into your homedir")
private def installTask = task {
println("Hello world!")
}
override lazy val settings = super.settings ++
Seq(install <<= (oneJar in Global)(installTask dependsOn(_)))
lazy val root = Project(id = "buildbro",
base = file("."),
settings = Project.defaultSettings)
}
And here's the error I'm getting:
[error] Reference to undefined setting:
[error]
[error] */*:one-jar from {.}/*:install
[error] Did you mean *:one-jar ?
[error]
Does anybody know what this means? I believe I have to scope the oneJar TaskKey in a different way. Thanks for any help you can offer.
I think something like this should work:
object BuildBroBuild extends Build {
val install = TaskKey[Unit]("install", "Installs the JAR and a launcher script into your homedir")
private lazy val installTask = install <<= (oneJar, streams) map { case (a, s) => {
// 'a' is the output from the onejar task (ie, the artifact)
println("Hello world!")
}
override lazy val settings = super.settings ++
Seq(installTask)
lazy val root = Project(id = "buildbro",
base = file("."),
settings = Project.defaultSettings)
}
Here, we are taking the output of the oneJar task (as well as streams, which allows for logging, etc) as input for our new task.