Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences and similarities between Tasks and Commands in SBT

Tags:

scala

sbt

I recently got instructed to not confuse tasks and commands on this answer, which made me first realize that there even exist a difference. During my research confusion raised even more and I must admit that I am not clearly able to separate both! I think the main problem is that the terminology is often used synomymously but the concepts are different, highly related and quite similar to some extend. Reading the documentation didn't give me much satisfaction. I don't want to explicitly show problems in the sbt documentation, so don't get me wrong about this, but I want you to see my current progress. I have marked my questions bold during the journey and added a number in front of it.

Research

The first resource I consulted was the Tasks and Commands section in the documentation, which only pointed to the getting started guide.

Getting Started

The getting started guide doesn't really explain much about the differences on this exact behalf. In particular the Defining tasks and settings section seem to introduce even more confusion, with the destinction between the types; Setting[T], Setting[Task[T]], Task[T] and the terminology of keys and its corresponding types.

A TaskKey[T] is said to define a task. Tasks are operations such as compile or package. They may return Unit (Unit is Scala for void), or they may return a value related to the task, for example package is a TaskKey[File] and its value is the jar file it creates.

This is a bit quirky but ok for now, thus Tasks are TaskKey instances with a result of type T.

Each time you start a task execution, for example by typing compile at the interactive sbt prompt, sbt will re-run any tasks involved exactly once.

So any task is available on the sbt prompt. So where is the difference to a command? On further sections both seem to be used synonymously, like here. The section More About Settings describes further:

Remember that some settings describe tasks, so this approach also creates dependencies between tasks.

So tasks may depend on each other, introduced by settings.

A plugin extends the build definition, most commonly by adding new settings. The new settings could be new tasks. For example, a plugin could add a codeCoverage task which would generate a test coverage report.

Plugins may introduce new tasks using settings.

Also remember from .sbt build definition that a setting has a fixed value until project reload, while a task is re-computed for every “task execution” (every time someone types a command at the sbt interactive prompt or in batch mode).

This makes me think a command is only the thing entered on the sbt prompt or directly to the terminal using batch mode. Further it arises an idea that the command only acts as a shallow front-end for each task. #1 Does every task has a corresponding command?

By defining triggered plugins, auto plugins can be used as a convenient way to inject custom tasks and commands across all subprojects.

Here I think commands may be setup separately - similar as tasks. However the section Running Commands talks about creating aliases for commands or tasks but doesn't tell anything about tasks. Again I feel like are these concepts may be the same, although I know both are different.

Tasks

Here is a list of features pertaining to tasks according the Tasks page:

  1. By integrating with the settings system, tasks can be added, removed, and modified as easily and flexibly as settings.
  2. Input Tasks use parser combinators to define the syntax for their arguments. This allows flexible syntax and tab-completions in the same way as Commands.
  3. Tasks produce values. Other tasks can access a task’s value by calling value on it within a task definition.
  4. Dynamically changing the structure of the task graph is possible. Tasks can be injected into the execution graph based on the result of another task.
  5. There are ways to handle task failure, similar to try/catch/finally.
  6. Each task has access to its own Logger that by default persists the logging for that task at a more verbose level than is initially printed to the screen.

Above the features it further says:

Settings are evaluated at project load time. Tasks are executed on demand, often in response to a command from the user.

Ok, so some tasks may be initiated by other means.

Commands

Finally the command page states:

A “command” looks similar to a task: it’s a named operation that can be executed from the sbt console.

So for now I think any call to a specified task or command coming from the sbt prompt or batch is being called a command. No matter if the command directs to a task or a command instance. #2 Thus does it make sense to distinguish between a definition and a call level, in order to reduce ambigouties? For instance: On the call level everything is a command, but on the definition level this is a Command or a TaskKey instance.

Here is a piece of code showing the general anatomy of a command definition:

val action: (State, T) => State = ???
val parser: State => Parser[T] = ???
val command: Command = Command("name")(parser)(action)

So the command's action is a state transition which makes it highly composable in terms of functional programming. In comparison to that a task being a TaskKey[T] instance works more like a simple function returning a result of type T. Point 3 in the feature list states that tasks produce values which makes me think of tasks more like pure-ish functions producing no to only some small side effects like logging as stated by point 6, except for tasks with return type of Unit.

However, a command’s implementation takes as its parameter the entire state of the build (represented by State) and computes a new State. This means that a command can look at or modify other sbt settings, for example. Typically, you would resort to a command when you need to do something that’s impossible in a regular task.

So I get the idea of a command being somewhat superior for some cases. Then I ask myself: Do tasks and commands share the same subset of features regarding the list of task features, like the obvious point 2? Point 1 states that arbritrary modifications to settings can take place due to the integration of the settings system, isn't that true for commands as well? Same for point 4, 5 and 6. #3 Can someone clarify this, especially the limitations and give reasons for when I should should objectively prefer using commands over tasks or a (counter) example when not to?

like image 985
isaias-b Avatar asked Oct 17 '15 13:10

isaias-b


1 Answers

Try to use tasks whenever you can. They are easier to understand and easier to write.

From the documentation:

Typically, you would resort to a command when you need to do something that’s impossible in a regular task.

That of course doesn't help if it's not specified what the limitations of tasks are. I however personally have taken it to mean: if you want to manipulate the state of the build, you can use commands.

An example of this is when you want to run a task with different settings.

Tasks give you access to other settings and tasks, commands will also give you access to the state and allow you to manipulate it. That however comes at a cost of unfamiliarity to other developers and increased complexity.

There might be cases where something can be expressed both as a task and a command. Here I'd take the approach of picking the one that feels easier.

like image 122
EECOLOR Avatar answered Nov 15 '22 06:11

EECOLOR