Workflow Description Language (WDL)

repository·wdl-1.3·Indexed 21 days ago

https://github.com/openwdl/wdl

An open standard for describing data processing workflows using a human-readable syntax. WDL allows users to define atomic units of computation via tasks and connect them into complex computation graphs via workflows. It supports scaling across HPC, Cloud, and local environments, featuring native support for conditional execution, dynamic resource allocation, and scatter-gather operations. The documentation covers the 1.3 specification, language versioning, task execution lifecycles, and migration guides from Draft-2 through WDL 1.1.

Tokens
51.4K
Snippets
135
Records
174
Agent score
75%

What's inside WDL

  1. What is Workflow Description Language (WDL)?

    wdl-1.3

    Workflow Description Language (WDL) is an open standard for describing data processing workflows using a human-readable/writeable syntax. It is designed to be accessible to software engineers, domain experts (like biologists), and production system operators.

    Core capabilities include:

    • Defining atomic units of computation: Using the task construct.
    • Connecting units into graphs: Using the workflow construct to build computation graphs.
    • Scaling execution: Effortlessly scaling graphs across multiple environments (HPC, Cloud, or Local).
    • Idiomatic patterns: Native support for conditional execution, dynamic resource allocation, and scatter-gather operations.
  2. WDL 1.3 Specification Overview

    wdl-1.3

    This document defines the Workflow Description Language (WDL) version 1.3 specification. WDL is used to describe workflows.

    Key versioning indicators in the specification:

    • ✨: Indicates new features introduced in version 1.3.
    • 🗑: Indicates features that are deprecated and will be removed in the next major version (WDL 2.0).

    To ensure an execution engine is compliant with WDL 1.3, it must pass 100% of the compliance tests provided by spectool.

  3. Use metadata sections for human-readable information

    wdl-1.3

    WDL provides two optional sections for storing metadata intended for human readers (e.g., for UI generation or documentation). These sections can be ignored by execution engines without affecting correctness.

    • meta: Task-level metadata (e.g., author, description).
    • parameter_meta: Metadata specific to task inputs or outputs. Every key in this section must correspond to a task input or output.

    Metadata Value Rules:

    • Allowed types: string, numeric, boolean primitives, arrays, and objects.
    • The special value null is allowed for undefined attributes.
    • Expressions are not allowed in metadata values.
    • Metadata objects behave like struct literals but do not require a Struct type name.
    version 1.3
    
    task ex_paramter_meta {
      input {
        File infile
        Boolean lines_only = false
        String? region
      }
    
      meta {
        description: "A task that counts the number of words/lines in a file"
      }
    
      parameter_meta {
        infile: {
          help: "Count the number of words/lines in this file"
        }
        lines_only: { 
          help: "Count only lines"
        }
        region: {
          help: "Cloud region",
          suggestions: ["us-west", "us-east", "asia-pacific", "europe-central"]
        }
      }
    
      command <<< 
        wc ~{if lines_only then '-l' else ''} < ~{infile} 
      >>>
    
      output {
         Int result = read_int(stdout())
      }
    }
  4. Understand Hidden and Scoped Types

    wdl-1.3

    WDL uses different visibility levels for types:

    • Hidden Types: These can only be instantiated by the execution engine and cannot be used in a declaration within a WDL file. Examples include Union (used for None values and certain function returns) and task.
    • Scoped Types: These can only be defined by the execution engine within a specific scope. Examples include hints, input, and output types.
    • Hidden Scoped Types: Types like task are available in both pre-evaluation contexts (requirements, hints, runtime) and post-evaluation contexts (command, output).
  5. Rules and restrictions for File and Directory functions

    wdl-1.3

    Functions that operate on File or Directory types follow specific execution rules:

    • Path Manipulation: Functions that only manipulate paths (without reading contents/attributes) can be called anywhere, regardless of whether the file exists.
    • Reading Files: Functions that read file attributes or contents can only be called if the input file exists. In a task, if a file is created during the command block, it can only be read in the output section (e.g., using stdout() or stderr()).
    • Writing Files: Functions that write files can be called anywhere. However, writing files within a workflow is discouraged as it may create permanent output files not explicitly named in an output section, potentially requiring the engine to persist them to cloud storage.
    • Failure Modes: If a function cannot read/write the entire contents of a file due to permissions, resource limits (like memory), or implementation-imposed size limits, the calling task or workflow will fail.
    • Implementation Detail: For functions writing to the file system, implementations should use random filenames in temporary directories to avoid conflicts.
  6. How String, File, and Directory equality works

    wdl-1.3

    String Equality

    String values are compared using the Unicode values of their characters. A character is considered less than another if its Unicode value is lower.

    File and Directory Equality

    File and Directory values are canonicalized upon creation. Two values are considered equal if they refer to the same underlying resource, regardless of their string representation (e.g., /home/user/file.txt and /home/user/../user/file.txt are equal).

    For Directory values, trailing slashes are ignored (e.g., /home/user/dir and /home/user/dir/ are equal).

    Comparison with Strings

    When comparing a File or Directory to a String, the String is first coerced to a File or Directory (and thus canonicalized) before the comparison is performed.

    workflow file_directory_equality {
      input {
        File file_a
        File file_b
        Directory dir_a
        Directory dir_b
      }
    
    # After canonicalization, these compare as equal
      Boolean files_eq = file_a == file_b
      Boolean dirs_eq = dir_a == dir_b
    
      call check_equality {
        file_a = file_a,
        file_b = file_b,
        dir_a = dir_a,
        dir_b = dir_b
      }
    }
  7. Prevent Cyclic References in WDL

    wdl-1.3

    All references to declarations must be acyclic. If you represent declarations as nodes in a graph where edges point to the declarations used in their initializers, the graph must contain no cycles. Cycles cause deadlocks because WDL evaluates values based on dependency availability rather than linear order.

    Common Pitfall: Cycles between Scatters If scatter A depends on a value from scatter B, and scatter B depends on a value from scatter A, the workflow is invalid. To fix this, create two separate scatters over the same input array instead of trying to link them cyclically.

    # Invalid: Cyclic dependency between scatters
    workflow my_workflow {
      input {
        Array[Int] as
        Array[Int] bs
      }
    
      scatter (a in as) {
        Int x_a = a
        Array[Int] y_a = y_b
      }
    
      scatter (b in bs) {
        Array[Int] x_b = x_a
        Int x_b = b
      }
    
      output {
        Array[Array[Int]] xs_output = x_b
        Array[Array[Int]] ys_output = y_a
      }
    }
  8. How scatter/gather works in WDL

    wdl-1.3

    The scatter statement provides a mechanism for parallel execution over an array. It consists of three parts:

    1. An expression that evaluates to an Array[X].
    2. A scatter variable (an identifier) that holds the current item of type X during each iteration.
    3. A body containing statements (declarations, calls, scatters, or conditionals) executed for each value in the collection.

    Key Behaviors:

    • Parallelism: Iterations may run in parallel (on multi-core systems) or on separate virtual machines (on cloud platforms).
    • Scoping: The scatter variable is only accessible within the scatter body. It is not accessible in the enclosing scope.
    • Implicit Array Promotion: Any declaration or call output T <name> inside a scatter body is automatically collected into an Array[T] <name> that is available in the enclosing scope. The ordering of the exported array is guaranteed to match the input array.
    • Nested Scatters: If scatters are nested, the resulting output types are also nested (e.g., a scatter inside a scatter results in Array[Array[T]]).
    version 1.3
    
    task say_hello {
      input { String greeting }
      command <<< printf "~{greeting}, how are you?" >>>
      output { String msg = read_string(stdout()) }
    }
    
    workflow test_scatter {
      input {
        Array[String] name_array = ["Joe", "Bob", "Fred"]
        String salutation = "Hello"
      }
    
      scatter (name in name_array) {
        String greeting = "~{salutation} ~{name}"
        call say_hello { greeting = greeting }
      }
    
      output {
        # say_hello.msg is promoted from String to Array[String]
        Array[String] messages = say_hello.msg
      }
    }
  9. Define and use Enums in WDL

    wdl-1.3

    Enums allow you to define a set of named constants. They can be implicitly typed (where the value type is inferred from the choices) or explicitly typed.

    Enum Types

    • Implicitly typed String: If choices are strings, the enum is String-valued.
    • Implicitly typed (no value): If choices have no assigned values, they are treated as identifiers.
    • Explicitly typed: You can specify the value type, such as Array[String] or Map[String, Int], to define related constants.

    Constraints

    • You cannot use string interpolation (e.g., "~{var}") in enum values.
    • You cannot use function calls (e.g., length()) in enum values.
    # Implicitly typed String enum
    enum FileKind {
      FASTQ,
      BAM
    }
    
    # Explicitly typed enum with Array[String] values
    enum Contigs[Array[String]] {
      Canonical = ["chr1", "chr2", "chr3", "chr4", "chr5"],
      All = ["chr1", "chr2", "chr3", "chr4", "chr5", "chrM", "chrX", "chrY"]
    }
    
    # Implicitly typed enum with Map[String, Int] values
    enum DefaultConfig {
      Fast = { "threads": 4, "memory_gb": 8 },
      Standard = { "threads": 8, "memory_gb": 16 },
      HighMem = { "threads": 16, "memory_gb": 64 }
    }
  10. Order of precedence for the + operator and comparisons

    wdl-1.3

    When using the + operator or equality/inequality operators (=, !=) with primitive operands, WDL follows a specific order of precedence to resolve ambiguity. For the + operator, which is overloaded for both numeric addition and String concatenation, the precedence is:

    1. (Int, Int) or (Float, Float): numeric addition/comparison
    2. (Int, Float): coerce Int to Float, then numeric addition/comparison
    3. (String, String): string concatenation/comparison
    4. (String, Y): coerce Y to String, then string concatenation/comparison
    5. Others: coerce X and Y to String, then string concatenation/comparison

    Example behavior:

    # Evaluates to "3.0": 1 is coerced to Float (1.0), then numeric addition is performed
    String s1 = "~{1 + 2.0}"
    
    # Evaluates to "3.01": 1 is coerced to String, then concatenated with s1
    String s2 = "~{s1 + 1}"
    
    # Evaluates to true: 1 is coerced to Float (1.0), then numeric comparison is performed
    Boolean b1 = 1 == 1.0
    
    # Evaluates to true: true is coerced to String, then string comparison is performed
    Boolean b2 = true == "true"
    
    # Evaluates to false: 1 and true are both coerced to String, then string comparison is performed
    Boolean b3 = 1 == true
    # No single runnable example provided for the whole precedence logic, but the snippet above demonstrates the rules.
  11. Accessing task outputs and exposing inputs

    wdl-1.3

    A call's outputs are available for use in other calls or as workflow outputs immediately after the call completes.

    Note: Only the output declarations of a task are accessible to the calling workflow. Call inputs and private declarations are not accessible. To use a task's input as a workflow-level value, you must explicitly copy it to a task output using a unique name.

    task greet {
      input { String greeting }
      command <<< printf "~{greeting}" >>>
      output { 
        # Expose the input by copying it to a new output name
        String greeting_out = greeting 
      }
    }
    
    workflow copy_input {
      input { String name }
      call greet { greeting = "Hello ~{name}" }
      output { 
        # Access the exposed input via the call identifier
        String greeting = greet.greeting_out 
      }
    }
  12. Compare compound types using equality operators

    wdl-1.3

    In WDL, you can compare compound types like Array, Map, Pair, Struct, and Object using the == (equality) and != (inequality) operators.

    Two compound values are considered equal if and only if:

    1. They are of the same type.
    2. They are the same length.
    3. All of their contained elements are equal.

    Note on Ordering: Since Arrays and Maps are ordered, the order of their elements must match for them to be equal. For example, [1, 2, 3] == [2, 1, 3] evaluates to false.

    Type Coercion: You can use type coercion to compare values of different but compatible types (e.g., comparing an Array[Int] to an Array[Float]).

    version 1.3
    
    workflow array_map_equality {
      output {
        # arrays and maps with the same elements in the same order are equal
        Boolean is_true1 = [1, 2, 3] == [1, 2, 3]
        Boolean is_true2 = {"a": 1, "b": 2} == {"a": 1, "b": 2}
    
        # arrays and maps with the same elements in different orders are not equal
        Boolean is_false1 = [1, 2, 3] == [2, 1, 3]
        Boolean is_false2 = {"a": 1, "b": 2} == {"b": 2, "a": 1}
      }
    }