1. 问题
    2. 如何将多个工作流连接在一起?
    3. 目标
    4. 学习如何从多个CWL工作流描述构建嵌套工作流。

    工作流是将多个工具组合起来执行更大的操作的方法。我们也可以把工作流看作是工具本身;一个CWL工作流可以作为另一个CWL工作流的一个步骤,如果工作流引擎支持子工作流特性需求:

    1. requirements:
    2. SubworkflowFeatureRequirement: {}

    下面是一个使用1st-workflow.cwl作为一个嵌套的工作流:
    nestedworkflows.cwl

    1. #!/usr/bin/env cwl-runner
    2. cwlVersion: v1.0
    3. class: Workflow
    4. inputs: []
    5. outputs:
    6. classout:
    7. type: File
    8. outputSource: compile/compiled_class
    9. requirements:
    10. SubworkflowFeatureRequirement: {}
    11. steps:
    12. compile:
    13. run: 1st-workflow.cwl
    14. in:
    15. tarball: create-tar/tar_compressed_java_file
    16. name_of_file_to_extract:
    17. default: "Hello.java"
    18. out: [compiled_class]
    19. create-tar:
    20. in: []
    21. out: [tar_compressed_java_file]
    22. run:
    23. class: CommandLineTool
    24. requirements:
    25. InitialWorkDirRequirement:
    26. listing:
    27. - entryname: Hello.java
    28. entry: |
    29. public class Hello {
    30. public static void main(String[] argv) {
    31. System.out.println("Hello from Java");
    32. }
    33. }
    34. inputs: []
    35. baseCommand: [tar, --create, --file=hello.tar, Hello.java]
    36. outputs:
    37. tar_compressed_java_file:
    38. type: File
    39. streamable: true
    40. outputBinding:
    41. glob: "hello.tar"

    CWL工作流可以像命令行工具一样作为一个步骤使用,它的CWL文件包含在run中。然后,工作流输入(inp和ex)和输出(classout)可以被映射为步骤的输入/输出。

    1. compile:
    2. run: 1st-workflow.cwl
    3. in:
    4. inp:
    5. source: create-tar/tar
    6. ex:
    7. default: "Hello.java"
    8. out: [classout]

    我们1st-workflow.cwl是用工作流输入参数化的,所以在运行它时,我们必须提供一个作业文件来表示tar文件和*.java文件名。这通常是最佳实践,因为这意味着它可以在多个父工作流中重用,甚至在同一个工作流中的多个步骤中重用。
    这里我们使用default:来硬编码“Hello”。作为ex输入,我们的工作流程也需要一个在inp的tar文件,我们将在create-tar步骤中准备它。在这一点上,重构第一个工作流可能是个好主意。使cwl具有更具体的输入/输出名称,因为这些名称也在其作为工具使用时出现。
    还可以采用不那么通用的方法,避免作业文件中的外部依赖关系。因此,在这个工作流中,我们可以在将其添加到tar文件之前,使用前面提到的InitialWorkDirRequirement需求生成一个硬编码的Hello.java文件。

    1. create-tar:
    2. requirements:
    3. InitialWorkDirRequirement:
    4. listing:
    5. - entryname: Hello.java
    6. entry: |
    7. public class Hello {
    8. public static void main(String[] argv) {
    9. System.out.println("Hello from Java");
    10. }
    11. }

    在这种情况下,我们的步骤可以假设Hello.java,而不是参数化,所以只要CWL工作流引擎支持shellcommandrequire,我们就可以使用更简单的参数形式:

    1. run:
    2. class: CommandLineTool
    3. requirements:
    4. ShellCommandRequirement: {}
    5. arguments:
    6. - shellQuote: false
    7. valueFrom: >
    8. tar cf hello.tar Hello.java

    注意这里使用了shellQuote: false,否则shell会尝试执行带引号的二进制文件”tar cf hello.tar Hello.java”。
    这里的>块意味着换行符被剥离,因此可以在多行中编写单个命令。类似地,我们上面使用的|将保留换行符,并与shellcommandrerequirement相结合,这将允许嵌入shell脚本。不过,在CWL中应该少用Shell命令,因为这意味着你“跳出”了工作流,不再获得可重用的组件、来源或可伸缩性。为了再现性和可移植性,建议只使用带有DockerRequirement提示的shell命令,这样命令就可以在可预测的shell环境中执行。
    你是否注意到我们没有将tar cf工具分割成一个单独的文件,而是将其嵌入到CWL工作流文件中?这通常不是最佳实践,因为工具无法重用。在本例中这样做的原因是,命令行是用文件名硬编码的,这些文件名只在此工作流中有意义。
    在本例中,我们必须在外部准备一个tar文件,但这只是因为我们的内部工作流被设计为将其作为输入。对内部工作流更好的重构是采用一个要编译的Java文件列表,这将简化其在其他工作流中作为工具步骤的使用。
    嵌套工作流是一个强大的特性,可以生成更高级的功能和可重用的工作流单元——但就像创建CWL工具描述一样,必须注意提高它在多个工作流中的可用性。