Welcome to the WDL quickstart guide 🚀! This brief guide gives an overview of all the important bits of the WDL language to ensure you become an expert in writing your own tasks and workflows. Before diving into an example, let's take a closer look at the major concepts within the language.
Tasks are the atomic units of computation within WDL. Tasks are comprised of a set
of inputs (defined within the input
section), a set of outputs (defined within the
output
section), and a command (defined within the command
section), which is simply
a Bash script to execute. Tasks also support defining requirements (defined within the
requirements
section) which dictate certain aspects of the task runtime environment.
Importantly, tasks are executed within a container to ensure
portability across different execution environments (e.g., your local computer, an HPC,
or the cloud).
Workflows string together tasks via their inputs and outputs into a larger
computation graph that can be executed. Workflows are arranged similarly to tasks
except that (a) they don't have a requirements
section and (b) they make available
more control flow facilities that aren't relevant within a task context. For example,
the workflow below uses both conditional execution (the if
statement) and a
scatter-gather (the scatter
keyword and messages
output). Notably, workflows can
also define inputs and outputs, and these generally serve as the global inputs and
outputs for the execution of a workflow.
JSON is used for specifying both inputs to and outputs from a workflow. In the code
block below the workflow, you can see the inputs that are specified for the top-level
name
parameter. You can also optionally provide a value for the is_pirate
parameter. Further, the output of the workflow is communicated back to you via JSON.
Executing workflows typically involves preparing the needed JSON files and reading the
outputs within the JSON returned from your execution engine.
For example, assume you wanted to write a task that greets someone, as defined in the
name
input, in multiple different languages. If that individual is a pirate, you might
even wish to greet them conditionally with Ahoy
! WDL allows you to express this in a
straightforward manner by (a) constructing the atomic computation you'd like to achieve
using a task
and (b) running that task for each of your greetings using a workflow
.
version 1.2
task say_hello {
input {
String greeting
String name
}
command <<<
echo "~{greeting}, ~{name}!"
>>>
output {
String message = read_string(stdout())
}
requirements {
container: "ubuntu:latest"
}
}
workflow main {
input {
String name
Boolean is_pirate = false
}
Array[String] greetings = select_all([
"Hello",
"Hallo",
"Hej",
(
if is_pirate
then "Ahoy"
else None
),
])
scatter (greeting in greetings) {
call say_hello {
greeting,
name,
}
}
output {
Array[String] messages = say_hello.message
}
}
If you were to run the example above with these inputs,
{
"main.name": "world",
// "main.is_pirate": true,
}
the output of the above workflow would be the following.
{
"messages": [
"Hello, world!",
"Hallo, world!",
"Hej, world!",
// "Ahoy, world!" is included is `is_pirate` is set to `true` above.
]
}
This workflow, though simple, demonstrates the how WDL accomplishes its main values: namely, its human-readable/writable style and its straightforward but powerful control flow abstractions. You can learn more about the values of the WDL language on the Overview page.