Pass sbt settings as arguments in fullRunTask - sbt

How can I get the value of a setting (say, name) and pass it as an argument to fullRunTask? I do not understand the implementation of fullRunTask.
For example:
lazy val foo = TaskKey[Unit]("foo")
fullRunTask(foo, Compile, "foo.Foo", name.value)
does not work because I can't reference name.value in this context.

Ok I got some help from Josh Suereth. Doing this with fullRunTask is a little more complex but the extra stuff it does (adding runner in myTask) does wasn't really necessary. Inlining the body of runTask did what I needed.
lazy val myTask = taskKey[Unit]("my custom run task")
myTask := {
val r = (runner in Compile).value
val input = name.value // or any other string setting(s)
val cp = (fullClasspath in Compile).value
toError(r.run("my.MainClass", data(cp), Seq(input), streams.value.log))
}

Related

try/finally with SBT tasks

I have an existing task called myTask, whose implementation I don't control.
I want to redefine it in this way:
myTask := {
val x = prepare()
try
myTask.value
finally
cleanup(x)
}
As you probably know, this code wouldn't work, as we don't control when myTask.value is executed.
prepare can be called with Def.sequential(), and cleanup with the andFinally construct. The only problem is how cleanup can get the return value of prepare().
Def.sequential{
Def.task{
prepare()
},
myTask
}.andFinally(cleanup(???))
One workaround is to use global variables, but this is a dirty hack.
Any ideas?
Related doc
I've tried to use global variables, and it works ok, even though it isn't the most elegant way to implement it.
I have:
project/MyTasks.scala
build.sbt
snippet in MyTasks.scala:
object MyTasks {
var x = Option.empty[String]
def prepare(): String = ???
def cleanup(x: String): Unit = ???
}
snippet in build.sbt:
myTask := Def.sequential{
Def.task{
MyTasks.x = Some(MyTasks.prepare())
},
myTask
}.andFinally {
MyTasks.cleanup(MyTasks.x.get)
MyTasks.x = None
}.value
In this way, we can get the state from prepare, and bypass SBT limitations.

Providing partial input, that comes from a setting to an input task

With this excerpt, I try to create a 2nd inputKey with some preapplied input coming from a setting:
val foo = inputKey[Unit]("....")
foo := { ... }
val foo2 = inputKey[Unit]("....")
foo2 := {
foo.partialInput(" "+name.value).evaluated
}
But I get the Illegal dynamic reference error, as the arguments of partialInput must be constant if I use evaluated.
What is the best way of solving this?
Similar questions, I've read before:
combining input task with dynamic task in sbt
SBT How to pass input from one inputTask to another inputTask
How to call inputTask from within another inputTask?
Using SBT 0.13.7.
Related documentation.
The technique shown in this example taken from the SBT reference documentation doesn't work:
lazy val run2 = inputKey[Unit]("Runs the main class twice: " +
"once with the project name and version as arguments"
"and once with command line arguments preceded by hard coded values.")
// The argument string for the first run task is ' <name> <version>'
lazy val firstInput: Initialize[String] =
Def.setting(s" ${name.value} ${version.value}")
// Make the first arguments to the second run task ' red blue'
lazy val secondInput: String = " red blue"
run2 := {
val one = (run in Compile).fullInput(firstInput.value).evaluated
val two = (run in Compile).partialInput(secondInput).evaluated
}

How to call inputTask from within another inputTask?

In a inputTask I'm programmatically calling another inputTask, e.g. testOnly, with parameter string as follows:
val readParams = inputKey[Unit]("reads version")
readParams := {
... // here some Parser code
val a = "*OnlyThisClassPls*"
testOnly.toTask(a)
}
Unfortunately instead of result I get an exception Illegal dynamic reference. Why?
I think I solved my problem.
I created a method which converts testOnly inputTask to dynamic task (taskDyn) with parameter
def testOnlyWithDynamicParams(params: String) = Def.taskDyn {
(testOnly in Test).toTask(params)
}
I defined an dynamic input task (inputTaskDyn) which uses method to convert and evaluates value at the end
readParams := Def.inputTaskDyn {
... // here some Parser code
val paramsForTestOnly = " *OnlyThisClassPls*"
testOnlyWithDynamicParams(paramsForTestOnly)
}.evaluated
I'm not sure if it is a best way but it works for me. If you know the better solution please correct me.

Is it possible to have a dictionary with a mutable array as the value in Swift

I am trying to do this:
var dictArray = [String:[String]]()
dictArray["test"] = [String]()
dictArray["test"]! += "hello"
But I am getting the weird error NSString is not a subtype of 'DictionaryIndex<String, [(String)]>'.
I just want to be able to add objects to an array inside a dictionary.
Update: Looks like Apple considers this a "known issue" in Swift, implying it will work as expected eventually. From the Xcode 6 Beta 4 release notes:
...Similarly, you cannot modify the underlying value of a mutable
optional value, either conditionally or within a force-unwrap:
tableView.sortDescriptors! += NSSortDescriptor(key: "creditName", ascending: true)
Workaround: Test the optional value explicitly and then assign the
result back:
if let window = NSApplication.sharedApplication.mainWindow {
window.title = "Currently experiencing problems"
}
tableView.sortDescriptors = tableView.sortDescriptors!
You can only do this
var dictArray = [String:[String]]()
dictArray["test"] = [String]()
var arr = dictArray["test"]!;
arr += "hello"
dictArray["test"] = arr
because dictArray["test"] give you Optional<[String]> which is immutable
6> var test : [String]? = [String]()
test: [String]? = 0 values
7> test += "hello"
<REPL>:7:1: error: '[String]?' is not identical to 'UInt8'
append also won't work due to the same reason, Optional is immutable
3> dictArray["test"]!.append("hello")
<REPL>:3:18: error: '(String, [(String)])' does not have a member named 'append'
dictArray["test"]!.append("hello")
^ ~~~~~~
BTW the error message is horrible...
You may use NSMutableArray instead of [String] as a value type for your dictionary:
var dictArray: [String: NSMutableArray] = [:]
dictArray["test"] = NSMutableArray()
dictArray["test"]!.addObject("hello")
This is still an issue in Swift 3. At least I was able to create method that can handle it for you.
func appendOrCreate(newValue: Any, toArrayAt key: String, in existingDictionary: inout [AnyHashable:Any]) {
var mutableArray = [Any]()
if let array = existingDictionary[key] as? [Any]{
//include existing values in mutableArray before adding new value
for existingValue in array {
mutableArray.append(existingValue)
}
}
//append new value
mutableArray.append(newValue)
//save updated array in original dictionary
existingDictionary[key] = mutableArray
}
The problem is that we want class semantics here but have to use structs. If you put class objects into the dictionary, you get what you want!
So, if you haveĀ¹ to have mutable values, you can wrap them in a class and perform updates with a closure:
class MutableWrapper<T> {
var rawValue: T
init(_ val: T) {
self.rawValue = val
}
func update(_ with: (inout T) -> Void) {
with(&self.rawValue)
}
}
Example:
func foo() {
var dict = [String: MutableWrapper<[String]>]()
dict["bar"] = MutableWrapper(["rum"])
dict["bar"]?.update({$0.append("gin")})
print(dict["bar"]!.rawValue)
// > ["rum", "gin"]
}
For what it's worth, I do not see a way to keep caller and wrapper in sync. Even if we declare init(_ val: inout T) we will end up with a copy in rawValue.
Performance is not necessarily an issue since the compiler optimizes structs heavily. I'd benchmark any mutable solution against what looks like lots of copy-updates in the code.
Since Swift 4.1 you can provide a default value to the subscript which allows you to solve this quite naturally now:
dictArray["test", default: []].append("hello")

How can a duplicate class be excluded from sbt assembly?

We have a situation in which two dependencies have exactly the same class (because one of the dependencies copied it and included in their own source).
This is causing sbt assembly to fail its deduplication checks.
How can I exclude a class from a particular jar?
You need a mergeStrategy, which will take one of the files.
mergeStrategy in assembly := {
case PathList("path", "to", "your", "DuplicatedClass.class") => MergeStrategy.first
case x => (mergeStrategy in assembly).value(x)
}
Update
If you want to handle the file depending on the JAR which it came from, I don't think you can with the merge strategies that assembly plugin defines. What you could do you could define your own strategy.
I would invert your condition though. I think the question should be "How can I include a class from a particular JAR?". The reason is that there can be more than two JARs having the same class, and you can only include one in the end.
You can tell from where the file comes by using AssemblyUtils.sourceOfFileForMerge.
project/IncludeFromJar.scala
import sbtassembly._
import java.io.File
import sbtassembly.Plugin.MergeStrategy
class IncludeFromJar(val jarName: String) extends MergeStrategy {
val name = "includeFromJar"
def apply(args: (File, String, Seq[File])): Either[String, Seq[(File, String)]] = {
val (tmp, path, files) = args
val includedFiles = files.flatMap { f =>
val (source, _, _, isFromJar) = sbtassembly.AssemblyUtils.sourceOfFileForMerge(tmp, f)
if(isFromJar && source.getName == jarName) Some(f -> path) else None
}
Right(includedFiles)
}
}
build.sbt
mergeStrategy in assembly := {
case PathList("path", "to", "your", "DuplicatedClass.class") => new IncludeFromJar("jarname.jar")
case x => (mergeStrategy in assembly).value(x)
}
What version of sbtassembly are yall using?
I believe Im using using a different version (0.14.2) because Im getting an error using use project/IncludeFromJar.scala.
When compiling I get the following error:
method apply cannot override final member
def apply(args: (File, String, Seq[File])): Either[String, Seq[(File, String)]] = {
^
Upon further investigation I found out that sbtassembly.MergeStrategy's apply method is final. Therefore, IncludeFromJar's apply method cannot override sbtassembly.MergeStrategy's even with specifying override def apply in IncludeFromJar
Thanks :)
Probably a bit late to the discussion but to help others that find this:
when you extend MergeStrategy you want to override the method apply with the signature:
def apply(tempDir: File, path: String, files: Seq[File]): Either[String, Seq[(File, String)]]
The apply method that is final with the tuple argument, calls the apply with the parameters split out as above
https://github.com/sbt/sbt-assembly/blob/edd35cfbaf05c3465371b63d38fda8ac579d766c/src/main/scala/sbtassembly/MergeStrategy.scala#L19
So the example becomes:
def includeFromJar(val jarName: String): sbtassembly.MergeStrategy = new sbtassembly.MergeStrategy {
val name = "includeFromJar"
def apply(tmp: File, path: String, files: Seq[File]): Either[String, Seq[(File, String)]] = {
val includedFiles = files.flatMap { f =>
val (source, _, _, isFromJar) = sbtassembly.AssemblyUtils.sourceOfFileForMerge(tmp, f)
if(isFromJar && source.getName == jarName) Some(f -> path) else None
}
Right(includedFiles)
}
}
mergeStrategy in assembly := {
case PathList("path", "to", "your", "DuplicatedClass.class") => includeFromJar("jarname.jar")
case x => (mergeStrategy in assembly).value(x)
}

Resources