How to sequentially call an input task and other tasks in sbt - scala

I am trying to override the it:run task in sbt so that it would run two other tasks (actually three if you include logging) in sequence. The first of these tasks is to provision the application (it:provision) and the second are to actually run the tests (it:test).
I started of with a simple Task[Unit] for my custom provision task.
lazy val provision = taskKey[Unit]("Provisions the application in an environment based on the configuration.")
I then defined a function to use for the it:run implementation as:
def runIntegrationTestsImpl(): Initialize[Task[Unit]] = Def.inputTask {
Def.sequential(
provision in IntegrationTest,
Def.task(state.value.log.info("Running integration tests.")),
test in IntegrationTest
).value
}
This worked fine. However what I really wanted was to make the provision task an InputTask[Unit].
So I then changed provision to:
lazy val provision = inputKey[Unit]("Provisions the application in an environment based on the configuration.")
And tried to update the it:run implementation:
def runIntegrationTestsImpl(): Initialize[InputTask[Unit]] = {
val parser = DefaultParsers.any.*.map(_.mkString)
Def.inputTask {
val prov = (provision in IntegrationTest).toTask(parser.parsed)
Def.sequential(
prov,
Def.task(state.value.log.info("Running integration tests.")),
test in IntegrationTest
).value
}
}
This is as close as I got to it compiling. I didn't really intend on parsing the arguments here but it seemed toTask was the only way to get an Initialize[Task[Unit]].
No matter what I try and do I still cannot get rid of the following error:
Illegal dynamic reference: prov
Which is referring to the first element in the sequence.
I seem to hit this problem a lot with the sbt macros. Is there a way to achieve what I want?

Related

How to execute scala tests programmatically

I'm looking for a way to execute scala tests (implemented in munit, but it could be also ScalaTest) programmatically. I want to perform more or less what sbt test does out-of-the box inside my own scala code, without running sbt (focusing on test discovery and execution and getting back a report).
I some something like this in mind:
object Test extends App {
val tests = TestDiscovery.discover("package.that.has.tests")
val reports = tests.foreach(test => test.execute())
// do something with the reports, maybe print to console
}
Is there any documentation related to this?
Scala Test has execute() and run().
In order to understand the impact of all the args it's worth looking at the Scala Test shell as well

Why value method cannot be used outside macros?

The error message
`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
val x = version.value
^
clearly indicates how to fix the problem, for example, using :=
val x = settingKey[String]("")
x := version.value
The explanation in sbt uses macros heavily states
The value method itself is in fact a macro, one that if you invoke it
outside of the context of another macro, will result in a compile time
error, the exact error message being...
And you can see why, since sbt settings are entirely declarative, you
can’t access the value of a task from the key, it doesn’t make sense
to do that.
however I am confused what is meant by declarative nature of sbt being the reason. For example, intuitively I would think the following vanilla Scala snippet is semantically similar to sbt's
def version: String = ???
lazy val x = s"Hello $version" // ok
trait Foo {
def version: String
val x = version // ok
}
As this is legal, clearly the Scala snippet is not semantically equivalent to the sbt one. I was wondering if someone could elaborate on why value cannot be used outside macros? Is the reason purely syntactic related to macro syntax or am I missing something fundamental about sbt's nature?
As another sentence there says
Defining sbt’s task engine is done by giving sbt a series of settings, each setting declaring a task implementation. sbt then executes those settings in order. Tasks can be declared multiple times by multiple settings, the last one to execute wins.
So at the moment when the line
val x = version.value
would be executed (if it were allowed!), that whole program is still being set up and SBT doesn't know the final definition of version.
In what sense is the program "still being set up"?
SBT's order of actions is, basically (maybe missing something):
All your Scala build code is run.
It contains some settings and tasks definitions, SBT collects those when it encounters (along with ones from core, plugins, etc.)
They are topologically sorted into the task graph, deduplicated ("the last one to execute wins"), etc.
Settings are evaluated.
Now you are allowed to actually run tasks (e.g. from SBT console).
version.value is only available after step 4, but val x = version.value runs on step 1.
Would not lazy evaluation take care of that?
Well, when you write val x = ... there is no lazy evaluation. But lazy val x = ... runs on step 1 too.

call a Task a second time for side effects

I am trying to write some tests around my plugin https://github.com/fommil/sbt-big-project to assert that unnecessary work is not being performed on invocations of various commands.
However, it seems that it is not possible to invoke a Task a second time, even in a Def.taskDyn. e.g. in the below, nothing happens between "HELLO" and "GOODBYE" as the AST rewrite from the sbt macros is obviously just re-using the same reference:
val testFastCompileTask = Def.taskDyn {
(compile in Compile).value
println("HELLO WORLD!")
Def.task {
(compile in Compile).value
println("GOODBYE WORLD!")
}
}
is there any way to force the task to run the second time?
Even creating a dummy task that simply runs compile, doesn't invoke the compile a second time. I can confirm that in the sbt REPL, typing compile a second time definitely is doing a bunch of stuff on the screen.
No, deduplication still occurs since normally you don't want compilation to happen twice in this context. You can define your own command or use scripted plugin (as you're now doing) if it's for testing.

Using input args inside a TaskKey

I'm writing an sbt plugin, and have created a TaskKey that need to get parsed arguments
lazy val getManager = TaskKey[DeployManager]("Deploy manager")
lazy val getCustomConfig = InputKey[String]("Custom config")
...
getCustomConfig := {
spaceDelimited("<arg>").parsed(0)
}
getManager := {
val conf = configResource.evaluated
...
}
but I get this error during compilation:
`parsed` can only be used within an input task macro, such as := or Def.inputTask.
I can't define getManager as InputKey since I later use it's value many times, and for an inputKey the value gets created anew on each evaluation (and I need to use the same instance)
You cannot do what you want in a reasonable way in sbt. (And the type system nicely prevents you from doing that in this case).
Imagine that getManager is a TaskKey that takes parsed arguments (a note aside, the sbt way of naming this would probably be manager, get is implied).
I now decide that, for example, compile depends on getManager. If I type compile in the shell, what arguments should getManager parse?
There is no concept of arguments inside the sbt dependency tree. They are just a shallow (and IMHO somewhat hackish) addition to make for a nicer CLI.
If you want to make getManager configurable, you can add additional settings, getManager depends on and then use set on the command line to change these where necessary.
So in you case:
lazy val configResource = SettingKey[...]("Config resource")
getManager := {
val conf = configResource.value
// ...
}

Create a new task that runs a program

I need to define a custom tasks that computes the name of a main class and then runs it. I was thinking about something like this
customTask {
mainClass = compute main class name based on env
runMain(mainClass, jvm-args, fork=true)
}
and then in SBT i would be able to run
sbt> custom-task
can this be done in SBT 11.2 ?.
well you can give it a try .. I works fine for me -
lazy val testngRun = inputKey[Unit]("custom run task for testng")
testngRun := {
val one = (runMain in Compile).fullInput(" org.testng.TestNG -testclass com.pg.acceptance.testcase.PfsLoginServiceTest").evaluated
}
Late answer but you can create new SBT tasks as mentioned in documentation http://www.scala-sbt.org/release/docs/Detailed-Topics/Tasks#defining-a-new-task
You can run any scala code as the task code. Tasks can also take input arguments.
Pretty much powerful IMO.