Task Inputs

Declaring and managing task input parameters

A task's input section declares its input parameters. The values for declarations within the input section may be specified by the caller of the task. An input declaration may be initialized to a default expression to use when the caller does not supply a value. Input declarations with the optional type quantifier ? also may be omitted by the caller even when there is no default initializer. If an input declaration has neither an optional type nor a default initializer, then it is a required input, meaning the caller must specify a value.

Example: task_inputs_task.wdl

version 1.3

task task_inputs {
  input {
    Int i               # a required input parameter
    String s = "hello"  # an input parameter with a default value
    File? f             # an optional input parameter
    Directory? d = "/etc"             # an optional input parameter with a default value
  }

  command <<<
  for i in 1..~{i}; do
    printf "~{s}\n"
  done
  if ~{defined(f)}; then
    cat ~{f}
  fi
  >>>
}

Example input:

{
  "task_inputs.i": 1
}

§Task Input Localization

File and Directory inputs may require localization to the execution environment. For example, a file located on a remote web server that is provided to the execution engine as an https:// URL must first be downloaded to the machine where the task is being executed.

  • Files and Directorys are localized into the execution environment prior to evaluating any expressions. This means that references to File or Directory declarations in input declaration expressions, private declaration expressions, and the command section are always replaced with the local paths to those files/directories.
  • When multiple input declarations refer to the same canonicalized File or Directory (i.e., they compare as equal), the execution engine should localize the resource once, and all references to those declarations should resolve to the same localized path.
  • When localizing a File or Directory, the engine may choose to place the local resource wherever it likes so long as it adheres to these rules:
    • The original file/directory name (the "basename") must be preserved even if the path to it has changed.
    • Two distinct input files with the same basename must be located separately, to avoid name collision. Note that this refers to two different files (that would not compare as equal), not to multiple input declarations that reference the same underlying file.
    • Two input files that originate from the same "parent" must be localized into the same directory for task execution.
      • For local paths, "parent" means the parent directory.
      • For remote paths specified as a URI, "parent" means the entire URI up to the last '/' of the path (i.e., excluding the final component and any parameters). For example, http://foo.com/bar/a.txt and http://foo.com/bar/b.txt have the same parent (http://foo.com/bar/), so they must be localized into the same directory.
      • For remote paths specified by other means, it is up to the execution engine to determine what is meant by "parent".
    • See the special case handling for Versioning Filesystems below.
  • When a WDL author uses a File or Directory input in their Command Section, the absolute path to the localized file/directory is substituted when that declaration is referenced.

The above rules do not guarantee that two files will be localized to the same directory unless they originate from the same parent location. If you are writing a task for a tool that assumes two files will be co-located, then it is safest to manually co-locate them prior to running the tool. For example, the following task runs a choice caller (varcall) on a BAM file and expects the BAM's index file (.bai extension) to be in the same directory as the BAM file.

task call_choices_safe {
  input {
    File bam
    File bai
  }

  String prefix = basename(bam, ".bam")

  command <<<
  mkdir workdir
  ln -s ~{bam} workdir/~{prefix}.bam
  ln -s ~{bai} workdir/~{prefix}.bam.bai
  varcall --bam workdir/~{prefix}.bam > ~{prefix}.vcf
  >>>

  output {
    File vcf = "~{prefix}.vcf"
  }
}

Runtime engines should treat input Files and Directorys as read-only, e.g., by setting their permissions appropriately on the local file system, or by localizing them to a directory marked as read-only.

Note starting in WDL 2.0 engines must treat input Files and Directorys as read-only.

A common pattern for tasks that require multiple input files to be in the same directory is to create a new directory in the execution environment and soft-link the files into that directory.

task two_files_one_directory {
  input {
    File bam
    File bai
  }
  String prefix = basename(bam, ".bam")
  command <<<
  mkdir inputs
  ln -s ~{bam} inputs/~{prefix}.bam
  ln -s ~{bai} inputs/~{prefix}.bam.bai
  varcall inputs/~{prefix}.bam
  >>>
}

§Special Case: Versioning Filesystem

Two or more versions of a file in a versioning filesystem might have the same name and come from the same directory. In that case, the following special procedure must be used to avoid collision:

  • The first file is always placed according to the rules above.
  • Subsequent files that would otherwise overwrite this file are instead placed in a subdirectory named for the version.

For example, imagine two versions of file fs://path/to/A.txt are being localized (labeled version 1.0 and 1.1). The first might be localized as /execution_dir/path/to/A.txt. The second must then be placed in /execution_dir/path/to/1.1/A.txt

§Input Type Constraints

Recall that a type may have a quantifier:

  • ? means that the input is optional and a caller does not need to specify a value for the input.
  • + applies only to Array types and it represents a constraint that the Array value must contain one-or-more elements.

The following task has several inputs with type quantifiers:

Example: input_type_quantifiers_task.wdl

version 1.3

task input_type_quantifiers {
  input {
    Array[String]  a
    Array[String]+ b
    Array[String]? c
    # If the next line were uncommented it would cause an error
    # + only applies to Array, not File
    #File+ d
    # An optional array that, if defined, must contain at least one element
    Array[String]+? e
  }

  command <<<
    cat ~{write_lines(a)} >> result
    cat ~{write_lines(b)} >> result
    ~{if defined(c) then
    "cat ~{write_lines(select_first([c]))} >> result"
    else ""}
    ~{if defined(e) then
    "cat ~{write_lines(select_first([e]))} >> result"
    else ""}
  >>>

  output {
    Array[String] lines = read_lines("result")
  }

  requirements {
    container: "ubuntu:latest"
  }
}

Example input:

{
  "input_type_quantifiers.a": [],
  "input_type_quantifiers.b": ["A", "B"],
  "input_type_quantifiers.e": ["C"]
}

Example output:

{
  "input_type_quantifiers.lines": ["A", "B", "C"]
}

If these input values are provided:

inputvalue
a["1", "2", "3"]
b[]

the task will fail with an error, because test.b is required to have at least one element.

On the other hand, if these input values are provided:

varvalue
a["1", "2", "3"]
b["x"]

the task will run successfully (c and d are not required). Given these values, the command would be instantiated as:

cat /tmp/file1 >> result
cat /tmp/file2 >> result

If the inputs were:

varvalue
a["1", "2", "3"]
b["x", "y"]
c["a", "b", "c", "d"]

then the command would be instantiated as:

cat /tmp/file1 >> result
cat /tmp/file2 >> result
cat /tmp/file3 >> result

§Optional inputs with defaults

Inputs with default initializers are implicitly optional: callers may omit the input or supply None whether or not its declared type carries the optional quantifier ?. Usually, inputs with defaults should omit the ? from their type, except when callers need the ability to override the default with None.

In detail, if a caller omits an input from the call input: section, then the default initializer applies whether or not the input type is declared optional. But if the caller explicitly supplies None for the input (either literally or by passing an optional value), then the default initializer applies only if the declared type isn't optional. This table illustrates the value taken by an input x depending on what the caller supplies:

input declaration:Int x = 1Int? x = 1Int? xInt x
call input: x = 4242424242
call input: x = None1NoneNoneerror
call input: omitted11Noneerror

Example: optional_with_default.wdl

version 1.3

task say_hello {
  input {
    String name
    String? salutation = "hello"
  }

  command <<< >>>

  output {
    String greeting = if defined(salutation) then "~{salutation} ~{name}" else name
  }
}

workflow optional_with_default {
  input {
    String name
    Boolean use_salutation
  }

  if (use_salutation) {
    call say_hello {
      name = name
    }
  } else {
    call say_hello {
      name = name,
      salutation = None
    }
  }

  output {
    String greeting = say_hello.greeting
  }
}

Example input:

{
  "optional_with_default.name": "John",
  "optional_with_default.use_salutation": false
}

Example output:

{
  "optional_with_default.greeting": "John"
}