Introdução ao WDL — Parte 1

George Carvalho
9 min readFeb 28, 2021

--

O WDL (Workflow Description Language) é uma liguagem de script open-source que permite você especificar workflows de processos de uma forma fácil e com uma sintaxe simples (~palavras deles~).

Foi originalmente desenvolvida para a área de Genômica pelo instituto BROAD, mas pode ser usado em diversas outras áreas que trabalhem com dados.

WDL se lê “Widdle”.

Não vou entrar em comparações entre linguagens de workflow, que são várias. Para quem tem interesse, existe essa lista feita pelo pessoal do CWL (Common Workflow Language). Particularmente, eu tenho mais interesse por Nextflow (pretendo escrever tutoriais usando isso posteriormente). Mas comecei usando CWL, depois Nextflow, dei uma olhada em Snakemake e hoje tenho estudado WDL.

Isso depende muito do grupo que você está trabalhando e parecido com linguages de programação você deve entender onde melhor aplicadas elas seriam. WDL por exemplo tem uma relação muito forte com o pessoal da ferramenta GATK e o Terra (baseado no Google Cloud), assim já existem diversos scripts que você pode reutilizar, por exemplo no BioWDL você já encontra alguns.

Aprendendo WDL

Iremos trabalhar os tutoriais do “learn-wdl”, para isso faça download do repositório e abra a pasta com seu editor de código.

Para o visual studio recomendo baixar a extensão “WDL DevTools

$ git clone https://github.com/openwdl/learn-wdl.git

Ambiente do WDL

Usei como referência esse material do learn-wdl. Eu estou trabalhando em cima do Ubuntu Xenial (16.04). Primeiramente você precisa ter os seguintes pre-requisitos:

  • Java
$  java -version
openjdk version "1.8.0_275"
OpenJDK Runtime Environment (build 1.8.0_275-8u275-b01-0ubuntu1~16.04-b01)
OpenJDK 64-Bit Server VM (build 25.275-b01, mixed mode)

Para instalar você pode usar os seguintes comandos:

$ sudo apt install openjre-8-headless
# or
$ sudo apt update
$ sudo apt install default-jdk
  • Docker
$ docker --version
Docker version 17.06.1-ce, build 874a737

Para instalar o Docker:

$ curl -sS https://get.docker.com/ | sh
$ sudo usermod -aG docker $USER

Caso não funcione pra você procure na documentação do Docker.

  • Cromwell e Womtool
$ sudo java -jar cromwell-56.jar --version 
cromwell 56
$ sudo java -jar womtool-56.jar --version
womtool 56

Para instalar:

$ mkdir cromwell; cd cromwell
$ curl -L -o cromwell-56.jar https://github.com/broadinstitute/cromwell/releases/download/56/cromwell-56.jar
$ curl -L -o womtool-56.jar https://github.com/broadinstitute/cromwell/releases/download/56/womtool-56.jar

É bom copiar o caminho desses .jar para usarmos depois pra rodar os scritps.

Também é bom dar uma olhada no github para obter a release mais recente das duas ferramentas.

1. Como fazer um “hello world”

Volte a pasta do learn-wdl. No caso eu fui para:

cd ../learn-wdl/1_script_examples/1_hello_worlds/1_hello

Vamos analisar o código hello.wdl que temos as três principais partes do WDL:

version 1.0workflow HelloWorld {
call WriteGreeting
}
task WriteGreeting {
command {
echo "Hello World"
}
output {
# Write output to standard out
File output_greeting = stdout()
}
}
  • Linhas 3–5 temos a definição do workflow que simplesmente chama uma task definida.
  • Linhas 7–15 nós temos a task que é um comando simples em bash para printar um “Hello” que vai ser um output com nome output_greeting.

2. Executando o script

  • WDL é o script que descreve o workflow.
  • Cromwell é um serviço em Java que organiza a execução (job scheduler) e pode ser configurado para rodar em diferentes serviços (AWS, GCP, etc).

Para começar vamos usar o Cromwell no modo Run que é normalmente utilizado para rodar protótipos, rodando workflows individuais. Podemos usar em uma máquina local (como fizemos anteriormente) ou em uma máquina virtual na nuvem.

Para trabalhar em produção normalmente se trabalhar com o modo Server. Usando uma máquina na nuvem que vai ser utilizada como o organizador de execuções (Java scheduler), assim poderemos rodar múltiplos workflows.

Então como rodamos o script?

$ sudo java -jar /caminho/para/cromwell-56.jar run hello.wdl

Verás que o output é bem verboso, que no meio em algum momento ele printou nosso “Hello World” com sucesso!

3. Sobre o modo Server

Quando vamos rodar nossas análises, nós normalmente utilizamos o modo Server. Você pode utilizar do jeito mais comum que é na “nuvem pública” ou no seu próprio HPC (High Performance Computing):

  • GCP — conjunto de VMs (máquinas virtuais) com life sciences APIs.
  • AWS — conjuntos de VMs com AWS batch.
  • Azure — conjuntos de VMs com Azure batch.
  • Terra.bio — Cromwell já disponível no ambiente da GCP.

A ideia é você usar grupos de VMs que são ativadas dinamicamente. Os criadores indicam usar o Terra.bio por ter sido criado pela BROAD. Para mais informações veja o tutorial do learn-wdl no YouTube [1].

4. Lidando com erros

Vamos analisar agora com o script x_hello-error.wdl que possui um erro.

  • Primeiramente tenha certeza de estar usando a versão mais atual do Cromwell. Assim você terá certeza que as especificações de linguagem e features são suportadas.
  • Para o modo Run que estamos usando aqui a ferramenta womtool.jar vai ajudar a detectar possíveis erros no nosso script.

Agora vamos tentar rodar esse script e ver o que ele retorna.

$ cd ../2_hello_docker
$ sudo java -jar /caminho/para/cromwell-56.jar run x_hello-error.wdl
...
Workflow f7b32c3a-6c99-4fdc-b161-4781fde97a2d transitioned to state Failed

Verás que ao final do processo uma mensagem indicando um Failed. Mas se você for para o meio do processo verá:

Failed to process workflow definition 'HelloWorld' (reason 1 of 1): Cannot generate outputs for 'call WriteGreeting'. No such callable exists in [WriteGreetings]

Com essa informação já podemos resolver o problema do script, mas vamos ver o que o womtool nos mostrará.

$ sudo java -jar /caminho/para/womtool-56.jar validate x_hello-error.wdl
Failed to process workflow definition 'HelloWorld' (reason 1 of 1): Cannot generate outputs for 'call WriteGreeting'. No such callable exists in [WriteGreetings]

Ele nos mostra o mesmo resultado, porém não precisamos ficar procurando no meio dos processos do comando do Cromwell, facilitando nossa vida. Existem outros comandos que podem ser vistos na documentação ou usando o --help .

Abrindo o arquivo para resolver o bug:

version 1.0 workflow HelloWorld {
call WriteGreeting
}
task WriteGreetings {
command {
echo "Hello World"
}
output {
File output_greeting = stdout()
}
runtime {
docker: "ubuntu:latest"
}
}

Vemos a adição de um “s” na task WriteGreetingsque deveria ser WriteGreeting. Resolvendo isso e rodando novamente obtemos sucesso no processo.

$ sudo java -jar /caminho/para/womtool-56.jar validate x_hello-error.wdl
Success!

Para o modo Server no Terra.bio ao tentar fazer upload de um script no FireCloud ele vai mostrar o mesmo output do womtool.

Dependendo do ambiente na nuvem é preciso ter no script a descrição de runtime com os atributos docker que na primeira versão não tinhamos.

5. Variáveis no script

O script exemplo agora é o ~/learn-wdl/1_script_examples/1_hello_worlds/3_hello_input_task/input-task.wdl

version 1.0workflow HelloInput {
call WriteGreeting
}
task WriteGreeting {
input {
String name
}
command {
echo 'hello ${name}!'
}
output {
File response = stdout()
}
runtime {
docker: 'ubuntu:latest'
}
}

Primeiramente precisamos validar o script usando o womtool-56.jar como visto anteriormente:

$ sudo java -jar /caminho/para/womtool-56.jar validate input-task.wdl
Success!

Feito isso, vamos tentar rodar ele usando o Cromwell:

$ sudo java -jar /caminho/para/cromwell-56.jar run input-task.wdl
...
Workflow b13559c0-223d-4b08-9548-0a3a2ed1f111 transitioned to state Failed

Como vemos que falhou e subindo mais um pouco acharemos o motivo:

[2021-02-26 17:15:56,87] [info] WorkflowManagerActor Workflow b13559c0-223d-4b08-9548-0a3a2ed1f111 faile
d (during MaterializingWorkflowDescriptorState): cromwell.engine.workflow.lifecycle.materialization.Mate
rializeWorkflowDescriptorActor$$anon$1: Workflow input processing failed:
Required workflow input 'HelloInput.WriteGreeting.name' not specified

Então claramente ele quebrou porque não recebeu a variável esperada, nessa caso o womtool pode nos ajudar. Nele existe um modo inputs que indica se o script possui variáveis a serem dadas de entrada.

$ sudo java -jar /caminho/para/womtool-56.jar inputs input-task.wdl
{
"HelloInput.WriteGreeting.name": "String"
}

Esse comando nos retorna o que precisamos dar de entrada, no caso deveria ser um inputs.json no formato indicado pelo resultado do comando. Para facilitar nossa vida, podemos jogar isso para um arquivo e manipular "String" com o que queremos e então rodar o script novamente passando esse arquivo dessa vez.

$ sudo java -jar /caminho/para/womtool-56.jar inputs input-task.wdl > inputs.json
$ vim inputs.json
$ cat inputs.json
{
"HelloInput.WriteGreeting.name": "Input"
}
$ sudo java -jar /caminho/para/cromwell-56.jar run input-task.wdl --inputs inputs.json
...
[2021-02-26 17:28:10,18] [info] BackgroundConfigAsyncJobExecutionActor [fb3f8c16HelloInput.WriteGreeting:NA:1]: echo 'hello Input!'

Pensando no modo Server existe esse artigo no Terra super bem exemplificado de como usar variáveis lá. Além disso, esse artigo lá fala sobre os tipos de variáveis disponíveis que é bem útil na hora de você implementar seu pipeline mais na frente.

Tente fazer o mesmo com o script em ~/learn-wdl/1_script_examples/1_hello_worlds/4_hello_input_workflow/input-workflow.wdl .

6. Arquivos e variáveis de ambiente

Agora trabalharemos em cima do script ~/learn-wdl/1_script_examples/1_hello_worlds/5_hello_file/hello-file.wdl .

version 1.0workflow HelloFile {
input {
File file_input
Int mem_gb
}
call InputFile { input: file_input=file_input, mem_gb=mem_gb }
}
task InputFile {
input {
File file_input
Int mem_gb

}
command {
bash echo 'The file is ${file_input}!' ${mem_gb}
}
output {
File result = stdout()
}
runtime {
docker: "ubuntu:latest"
memory: mem_gb + "GB"
# keep going even ...
continueOnReturnCode: 126
}
}

Aqui vamos usar o conceito anterior de passar um .json como entrada, no caso o arquivo que está na mesma pasta.

$ sudo java -jar /caminho/para/cromwell-56.jar run hello-file.wdl --inputs hello-file.json
...
[2021-02-26 18:28:39,47] [warn] Local [1592ea4f]: Key/s [memory] is/are not supported by backend. Unsupp
orted attributes will not be part of job executions.
...

Apesar de rodar com sucesso, temos essa observação de warn a ser analisada em meio a todos os processos printados na tela. Isso se chama memory assignment, só para mostrar que os parâmetros dependem do ambiente de execução.

Só pra lembrar que não precisa estar na pasta dos scripts para rodá-los, você pode passar o caminho+script que ele será rodado normalmente.

$ sudo java -jar /caminho/para/cromwell-56.jar run /caminho/para/hello-file.wdl --inputs /caminho/para/hello-file.json

É comum usarmos arquivos que estejam salvos em buckets na nuvem, então é possível por esses arquivos no seu .json de entrada (com a devida permissão de acesso ao arquivo no bucket) para o script em WDL. Por exemplo:

$ cat hello-file.json
{
"HelloFile.file_input": "https://storage.googleapis.com/wdl-quickstart/wdl-data/input.txt",
"HelloFile.mem_gb": 5
}

Isso é interessante uma vez que vamos rodar pipelines em VMs que estaram disponíveis apenas para gerar os resultados do pipeline, então arquivos que devem persistir para uso no pipeline podem ser incorporados assim.

7. Scripts mais legíveis

Abrindo o script em ~/learn-wdl/1_script_examples/1_hello_worlds/6_alias_task/hello-again.wdl vemos:

version 1.0workflow HelloWorldWithDocker {
call WriteGreeting as wg1
call WriteGreeting as wg2
}

task WriteGreeting {
command {
echo "Hello Docker"
}
output {
File output_greeting = stdout()
}
runtime {
docker: "ubuntu:latest"
}
}

Aqui é apenas para observar a possibilidade de criar alias com as task que usamos, no exemplo está repitdo a call mas com alias diferentes que podemos ver no output quando rodamos o script.

$ sudo java -jar /caminho/para/cromwell-56.jar run /caminho/para/hello-again.wdl
...
{
"outputs": {
"HelloWorldWithDocker.wg1.output_greeting": "/caminho/para/learn-wdl/cromwell-executions/Hello
WorldWithDocker/66bbb0cc-e015-4e02-ab87-75ad90eab1e1/call-wg1/execution/stdout",
"HelloWorldWithDocker.wg2.output_greeting": "/caminho/para/learn-wdl/cromwell-executions/Hello
WorldWithDocker/66bbb0cc-e015-4e02-ab87-75ad90eab1e1/call-wg2/execution/stdout"
},
"id": "66bbb0cc-e015-4e02-ab87-75ad90eab1e1"
}

Existe outro exemplo em ~/learn-wdl/1_script_examples/1_hello_worlds/6_alias_task/hello-task.wdl caso queira refazer o processo. Mas para esse script nos vamos rodar um script que o importará para fazer a chamada das task contidas, isso se chama subworkflow. O script maior é o ~/learn-wdl/1_script_examples/1_hello_worlds/6_alias_task/hello-workflow.wdl

version 1.0
import "hello-task.wdl" as HelloTask
workflow HelloWorldWithDocker {
call HelloTask.WriteGreeting
call HelloTask.WriteGreeting as wg
}

Rodamos esse script que tem duas call para a mesma tasksó para exemplificar que sem o alias essa chamada daria erro por estar repetida. Além de introduzir o conceito de import e poder reutilizar scripts menores em processos maiores.

$ sudo java -jar /caminho/para/cromwell-56.jar run /caminho/para/hello-workflow.wdl
...
{
"outputs": {
"HelloWorldWithDocker.WriteGreeting.output_greeting": "/caminho/para/learn-wdl/cromwell-execut
ions/HelloWorldWithDocker/07064989-49e1-467d-9b26-23b3c65d0e1d/call-WriteGreeting/execution/stdout",
"HelloWorldWithDocker.wg.output_greeting": "/caminho/para/learn-wdl/cromwell-executions/HelloW
orldWithDocker/07064989-49e1-467d-9b26-23b3c65d0e1d/call-wg/execution/stdout"
},
"id": "07064989-49e1-467d-9b26-23b3c65d0e1d"
}
...

Use os scripts em ~/learn-wdl/1_script_examples/1_hello_worlds/7_subworkflow/hello-workflow.wdl para treinar tudo que vimos sobre rodar workflow e inputs.

Aqui eu termino a primeira parte da introdução, de acordo com o curso do learn-wdl olhamos todos os arquivos da pasta ~/learn-wdl/1_script_examples/1_hello_worlds/ . Na parte dois que estou escrevendo olharemos a pasta ~/learn-wdl/1_script_examples/2_language_patterns/ , pelo menos essa é a ideia. Esse tutorial pode mudar a medida que eu for estudando, então se ligue nas referências.

Sobre o autor

Olá, sou George Carvalho bioinformata no Hospital Israelita Albert Einstein e mestrando em ciência da computação no CIn da UFPE. Tenho interesse em aprender bioinformática, genética, estatística, ciência de dados, machine learning, deep learning usando meus aprendizados em saúde como projetos. Você pode me encontrar no LinkedIn ou no Twitter para conversar sobre qualquer coisa!

Referências

  1. Learn WDL Course playlist
  2. How to set up GCE VM for WDL
  3. What do I need to set up to write and execute WDL workflows?

Sign up to discover human stories that deepen your understanding of the world.

--

--

George Carvalho
George Carvalho

Written by George Carvalho

Interested in bioinformatics, datascience, machine learning, statistics, and deep learning.

No responses yet

Write a response