How exactly sbt figures out task names? - scala

Suppose there's something like this in build.sbt
val printMessage = taskKey[Unit]("Simple task")
printMessage := {
println("Hello")
}
How sbt figures out that this task is called printMessage and makes it available in CLI when there is no string with that text? I would understand if the code was something like val printMessage = taskKey[Unit]("printMessage", "description") but this really baffles me out

SBT has a macro, sbt.std.KeyMacro.taskKeyImpl which takes a String description and infers the task name from the defining val's name.
This macro is aliased to taskKey[T] in the sbt package object.
So when you call taskKey[Unit]("SimpleTask"), that's expanded to a macro that finds the val printMessage and uses that to infer the task key's name.

Related

Import methods on an object in Scala

I am trying to write a DSL with Scala. I would initially like to be able to write things like
defType "foo"
when using it.
I had thought that the following should work:
src/main/scala/Test.scala
class Dsl {
def defType(name: String) = "dummy"
}
object Dsl {
def apply() = new Dsl()
}
class UseDsl {
def foo() = {
val dsl = Dsl()
import dsl._
dsl defType "foo"
defType("foo")
defType "foo"
}
}
This fails to compile:
[error] Test.scala:15:17: ';' expected but string literal found.
[error] defType "foo"
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
Explicitly giving dsl works with spaces to separate the method names and arguments.
Implicitly using dsl and parentheses to indicate arguments vs method names works.
Attempting to use both of them together fails.
Is there a way to get that to work?
Once this is working, I plan to extend the DSL to support things like
defType "foo"
-- "bar1" :: "quz"
-- "bar2" :: "quz"
which would be equivalent to
dsl.defType("foo").
--(ImplicitClass("bar1", dsl).::("quz")).
--(ImplicitClass("bar2", dsl).::("quz"))
Is this something that I will be able to get to work? I think the ImplicitClass would work with a declaration like
def implicit ImplicitClass(a: String, implicit dsl: Dsl) = ...
but clearly, my understanding of how you can get Scala to add things to your code is imperfect.
If it won't work, what are some minimal additions that would let it work?
build.sbt
ThisBuild / organization := "test"
ThisBuild / version := "0.0.1-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.8"
//
// Projects
//
lazy val root = (project in file("."))
No, method argument is not valid. For infix method calls without parentheses, you have to do
val1 method1 val2 method2 val3 ...
This chain can end in a method without arguments or in the last method's argument, and the first one is not too safe.
Even for members of current type, you need to do this method1 ... and can't omit this as in this.method1(...).

Can a parsed inputTask be used to invoke a runTask in sbt?

I'm trying to use sbt as a general task runner (similar to rake/npm). I can get it to parse input the way I want through an inputTask, but I'm absolutely stumped how to use this to invoke a runTask/fullRunTask
val partners: List[Parser[String]] = List("foo", "bar")
val partnerParser = partners.reduce(_ | _)
val acceptArgs = (' ' ~> partnerParser ~ (' ' ~> StringBasic))
lazy val importDump = inputKey[Unit]("Import static data dump")
lazy val importDumpTask = importDump := {
val (arg1, arg2) = acceptArgs.parsed
// how can I make this call?
// ... runTask(Compile, "foo.bar.baz.DoIt.dispatch", arg1, arg2).value
}
I understand that you can't directly call tasks from other tasks, only "depend" on them so the above code won't work.
I know I can do something like
mainClass := Some("foo.bar.baz.DoIt.dispatch")
(runMain in Compile).toTask(s" foo.bar.baz.DoIt.dispatch $arg1 $arg2").value
But that means I can't use any of the parsing/autocomplete functionality.
So my question is:
How can I parse input with an inputTask, then call a main method in my code with the resulting arguments?
This is extremely painful to do in sbt. I would recommend writing a shell script (or using sbt's built-in Process support).
That said, it's possible to do this by writing a new Command that mutates the State object provided, adding the tasks you want to run as items in the remainingCommands field.

SBT InputKey with property-like arguments

Can someone help me create a SBT task that can support property-like arguments from command line?
lazy val myTask = inputKey[Unit]("my task")
myTask := {
if (directoryOpt.isEmpty) // directoryOpt comes from an optional command line argument: directory="~/downloads"
fullRunInputTask(inputKey, Compile, "example.MyTaskClass")
else
fullRunInputTask(inputKey, Compile, "example.MyTaskClass", directoryOpt.get)
}
Where the task can be run from command line like:
sbt myTask directory="~/downloads"
I did read the sbt doc at http://www.scala-sbt.org/0.13/docs/Input-Tasks.html. But it only explains how to create a task parser like sbt myTask option1 option2 which does not quite meet my need.
UPDATE:
I used jazmit's solution since that was an easy change. It works well! I will also try Mariusz's solution and update here.
You can use project/Build.scala along your build.sbt with your inputs. You can also use Commands instead of Tasks. Below, an example:
import sbt._
import Keys._
object CustomBuild extends Build {
def myTask = Command.args("myTask", "<name>"){ (state, args) =>
val argMap = args.map { s =>
s.split("=").toList match {
case n :: v :: Nil => n -> v
}
}.toMap
//println(argMap) //to see all argument pairs
//react on name in params list
println("Hi "+ argMap.getOrElse("name", "Unknown"))
state //Command can modify state, so you must to return it.
}
}
Now You have to add this command to you project, in build.sbt add
commands += myTask
Now you can use it:
> sbt "myTask name=Mario"
> Hi Mario
> sbt myTask
> sbt Hi Unknown
Hope, it'll help You!
more about commands:
you can find here
You can use environmental properties to achieve what you want quickly.
From the command line, set a property as follows:
sbt myTask -Ddirectory="~/downloads"
From the task, you can retrieve the value as follows:
val directory = System.getProperty("directory");
If you want to do something more solid with syntax checking, tab completion, etc, you can define an input task as detailed here. If you need property=value syntax, you can define this using the parser combinator library, eg:
import sbt.complete.DefaultParsers._
val myArgs: Parser[String] = "directory=" ~> StringEscapable

How is an sbt task defined using <<= different from one defined with := that references another setting's .value?

I have the following example build.sbt that uses sbt-assembly. (My assembly.sbt and project/assembly.sbt are set up as described in the readme.)
import AssemblyKeys._
organization := "com.example"
name := "hello-sbt"
version := "1.0"
scalaVersion := "2.10.3"
val hello = taskKey[Unit]("Prints hello")
hello := println(s"hello, ${assembly.value.getName}")
val hey = taskKey[Unit]("Prints hey")
hey <<= assembly map { (asm) => println(s"hey, ${asm.getName}") }
//val hi = taskKey[Unit]("Prints hi")
//hi <<= assembly { (asm) => println(s"hi, $asm") }
Both hello and hey are functionally equivalent, and when I run either task from sbt, they run assembly first and print a message with the same filename. Is there a meaningful difference between the two? (It seems like the definition of hello is "slightly magical", since the dependency on assembly is only implied there, not explicit.)
Lastly, I'm trying to understand why hey needs the map call. Obviously it results in a different object getting passed into asm, but I'm not quite sure how to fix this type error in the definition of hi:
sbt-hello/build.sbt:21: error: type mismatch;
found : Unit
required: sbt.Task[Unit]
hi <<= assembly { (asm) => println(s"hi, $asm") }
^
[error] Type error in expression
It looks like assembly here is a [sbt.TaskKey[java.io.File]][2] but I don't see a map method defined there, so I can't quite figure out what's happening in the type of hey above.
sbt 0.12 syntax vs sbt 0.13 syntax
Is there a meaningful difference between the two?
By meaningful difference, if you mean semantic difference as in observable difference in the behavior of the compiled code, they are the same.
If you mean, any intended differences in code, it's about the style difference between sbt 0.12 syntax sbt 0.13 syntax. Conceptually, I think sbt 0.13 syntax makes it easier to learn and code since you deal with T instead of Initialize[T] directly. Using macro, sbt 0.13 expands x.value into sbt 0.12 equivalent.
anatomy of <<=
I'm trying to understand why hey needs the map call.
That's actually one of the difference macro now is able to handle automatically.
To understand why map is needed in sbt 0.12 style, you need to understand the type of sbt DSL expression, which is Setting[_]. As Getting Started guide puts it:
Instead, the build definition creates a huge list of objects with type Setting[T] where T is the type of the value in the map. A Setting describes a transformation to the map, such as adding a new key-value pair or appending to an existing value.
For tasks, the type of DSL expression is Setting[Task[T]]. To turn a setting key into Setting[T], or to turn a task key into Setting[Task[T]], you use <<= method defined on respective keys. This is implemented in Structure.scala (sbt 0.12 code base has simpler implementation of <<= so I'll be using that as the reference.):
sealed trait SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T] with Scoped.ListSetting[T, Id] { ... }
sealed trait TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[Task[T]] with Scoped.ScopingSetting[TaskKey[T]] with Scoped.ListSetting[T, Task] with Scoped.DefinableTask[T] { ... }
object Scoped {
sealed trait DefinableSetting[T] {
final def <<= (app: Initialize[T]): Setting[T] = setting(scopedKey, app)
...
}
sealed trait DefinableTask[T] { self: TaskKey[T] =>
def <<= (app: Initialize[Task[T]]): Setting[Task[T]] = Project.setting(scopedKey, app)
...
}
}
Note the types of app parameters. Setting key's <<= takes Initialize[T] whereas the task key's <<= takes Initialize[Task[T]]. In other words, depending on the the type of lhs of an <<= expression the type of rhs changes. This requires sbt 0.12 users to be aware of the setting/task difference in the keys.
Suppose you have a setting key like description on the lhs, and suppose you want to depend on name setting and create a description. To create a setting dependency expression you use apply:
description <<= name { n => n + " is good." }
apply for a single key is implemented in Settings.scala:
sealed trait Keyed[S, T] extends Initialize[T]
{
def transform: S => T
final def apply[Z](g: T => Z): Initialize[Z] = new GetValue(scopedKey, g compose transform)
}
trait KeyedInitialize[T] extends Keyed[T, T] {
final val transform = idFun[T]
}
Next, instead of description, suppose you want to create a setting for jarName in assembly. This is a task key, so rhs of <<= takes Initialize[Task[T]], so apply is not good. This is where map comes in:
jarName in assembly <<= name map { n => n + ".jar" }
This is implemented in Structure.scala as well:
final class RichInitialize[S](init: Initialize[S]) {
def map[T](f: S => T): Initialize[Task[T]] = init(s => mktask(f(s)) )
}
Because a setting key extends KeyedInitialize[T], which is Initialize[T], and because there's an implicit conversion from Initialize[T] to RichInitialize[T] the above is available to name. This is an odd way of defining map since maps usually preserves the structure.
It might make more sense, if you see similar enrichment class for task keys:
final class RichInitializeTask[S](i: Initialize[Task[S]]) extends RichInitTaskBase[S, Task] {...}
sealed abstract class RichInitTaskBase[S, R[_]] {
def map[T](f: S => T): Initialize[R[T]] = mapR(f compose successM)
}
So for tasks, map maps from a task of type S to T. For settings, we can think of it as: map is not defined on a setting, so it implicitly converts itself to a task and maps that. In any case, this let's sbt 0.12 users to think: Use apply for settings, map for tasks. Note that apply ever goes away for task keys as they extend Keyed[Task[T], Task[T]]. This should explain:
sbt-hello/build.sbt:21: error: type mismatch;
found : Unit
required: sbt.Task[Unit]
Then there's the tuple issue. So far I've discussed dependencies to a single setting. If you want to depend on more, sbt implicitly adds apply and map to Tuple2..N to handle it. Now it's expanded to 15, but it used to be up till only Tuple9. Seeing from a new user's point of view, the idea of invoking map on a Tuple9 of settings so it generates a task-like Initialize[Task[T]] would appear alien. Without changing the underlying mechanism, sbt 0.13 provides much cleaner surface to get started.

Is it possible to use scalap from a scala script?

I am using scalap to read out the field names of some case classes (as discussed in this question). Both the case classes and the code that uses scalap to analyze them have been compiled and put into a jar file on the classpath.
Now I want to run a script that uses this code, so I followed the instructions and came up with something like
::#!
#echo off
call scala -classpath *;./libs/* %0 %*
goto :eof
::!#
//Code relying on pre-compiled code that uses scalap
which does not work:
java.lang.ClassCastException: scala.None$ cannot be cast to scala.Option
at scala.tools.nsc.interpreter.ByteCode$.caseParamNamesForPath(ByteCode.
scala:45)
at scala.tools.nsc.interpreter.ProductCompletion.caseNames(ProductComple
tion.scala:22)
However, the code works just fine when I compile everything. I played around with additional scala options like -savecompiled, but this did not help. Is this a bug, or can't this work in principle? (If so, could someone explain why not? As I said, the case classes that shall be analyzed by scalap are compiled.)
Note: I use Scala 2.9.1-1.
EDIT
Here is what I am essentially trying to do (providing a simple way to create multiple instances of a case class):
//This is pre-compiled:
import scala.tools.nsc.interpreter.ProductCompletion
//...
trait MyFactoryTrait[T <: MyFactoryTrait[T] with Product] {
this: T =>
private[this] val copyMethod = this.getClass.getMethods.find(x => x.getName == "copy").get
lazy val productCompletion = new ProductCompletion(this)
/** The names of all specified fields. */
lazy val fieldNames = productCompletion.caseNames //<- provokes the exception (see above)
def createSeq(...):Seq[T] = {
val x = fieldNames map { ... } // <- this method uses the fieldNames value
//[...] invoke copyMethod to create instances
}
// ...
}
//This is pre-compiled too:
case class MyCaseClass(x: Int = 0, y: Int = 0) extends MyFactoryTrait[MyCaseClass]
//This should be interpreted (but crashes):
val seq = MyCaseClass().createSeq(...)
Note: I moved on to Scala 2.9.2, the error stays the same (so probably not a bug).
This is a bug in the compiler:
If you run the program inside an ide, for example Intellij IDEA the code is executed fine, however no fields names are found.
If you run it from command line using scala, you obtain the error you mentioned.
There is no way type-safe could should ever compiler and throw a runtime ClassCastException.
Please open a bug at https://issues.scala-lang.org/secure/Dashboard.jspa