diff --git a/src/sophios/cli.py b/src/sophios/cli.py index 4f8cbfa3..efea816b 100644 --- a/src/sophios/cli.py +++ b/src/sophios/cli.py @@ -27,6 +27,8 @@ help='Do not use the --provenance feature of CWL. (You should only disable provenance if absolutely necessary.)') parser.add_argument('--copy_output_files', default=False, action="store_true", help='Copies output files from the cachedir to outdir/ (automatically enabled with --run_local)') +parser.add_argument('--inline_cwl_runtag', default=False, action="store_true", + help='Copies cwl adapter file contents inline into the final .cwl in autogenerated/') parser.add_argument('--parallel', default=False, action="store_true", help='''When running locally, execute independent steps in parallel. diff --git a/src/sophios/main.py b/src/sophios/main.py index b1d91cca..b13078f1 100644 --- a/src/sophios/main.py +++ b/src/sophios/main.py @@ -161,6 +161,12 @@ def main() -> None: io.write_to_disk(rose_tree, Path('autogenerated/'), True, args.inputs_file) + # this has to happen after at least one write + # so we can copy from local cwl_dapters in autogenerated/ + if args.inline_cwl_runtag: + rose_tree = plugins.cwl_update_inline_runtag_rosetree(rose_tree, Path('autogenerated/'), True) + io.write_to_disk(rose_tree, Path('autogenerated/'), True, args.inputs_file) + if args.graphviz: if shutil.which('dot'): # Render the GraphViz diagram diff --git a/src/sophios/plugins.py b/src/sophios/plugins.py index 27cc085f..8206aa63 100644 --- a/src/sophios/plugins.py +++ b/src/sophios/plugins.py @@ -169,6 +169,32 @@ def cwl_update_outputs_optional(cwl: Cwl) -> Cwl: return cwl_mod +def cwl_update_inline_runtag(cwl: Cwl, path: Path, relative_run_path: bool) -> Cwl: + """Updates 'run' tag with inline content + + Args: + cwl (Cwl): A CWL Workflow + path (Path): The directory in which to read files from + relative_run_path (bool): Controls whether to use subdirectories or just one directory + Returns: + Cwl: A CWL Workflow with inline run (if any) + """ + cwl_mod = copy.deepcopy(cwl) + for step in cwl_mod['steps']: + runtag_orig = step.get('run', '') + if isinstance(runtag_orig, str) and runtag_orig.endswith('.cwl'): + if relative_run_path: + yml_path = Path.cwd() / path / runtag_orig + else: + yml_path = Path(runtag_orig) # Assume absolute path in the runtag + with open(yml_path, mode='r', encoding='utf-8') as f: + runtag_raw = yaml.safe_load(f.read()) + step['run'] = runtag_raw + else: + pass # We only care if the runtag is a cwl filepath + return cwl_mod + + Client = Union[docker.DockerClient, podman.PodmanClient] # type: ignore @@ -238,6 +264,30 @@ def remove_entrypoints_podman() -> None: remove_entrypoints(client, podman.domain.images_build.BuildMixin()) +def cwl_update_inline_runtag_rosetree(rose_tree: RoseTree, path: Path, relative_run_path: bool) -> RoseTree: + """Inlines the compiled CWL files runtag + + Args: + rose_tree (RoseTree): The data associated with compiled subworkflows + path (Path): The directory in which to read files from + relative_run_path (bool): Controls whether to use subdirectories or just one directory. + Returns: + RoseTree: rose_tree with inline cwl runtag + """ + n_d: NodeData = rose_tree.data + if n_d.compiled_cwl['class'] == 'Workflow': + outputs_inline_cwl_runtag = cwl_update_inline_runtag(n_d.compiled_cwl, path, relative_run_path) + else: + outputs_inline_cwl_runtag = n_d.compiled_cwl + + sub_trees_path = [cwl_update_inline_runtag_rosetree(sub_rose_tree, path, relative_run_path) for + sub_rose_tree in rose_tree.sub_trees] + node_data_path = NodeData(n_d.namespaces, n_d.name, n_d.yml, outputs_inline_cwl_runtag, n_d.tool, + n_d.workflow_inputs_file, n_d.explicit_edge_defs, n_d.explicit_edge_calls, + n_d.graph, n_d.inputs_workflow, n_d.step_name_1) + return RoseTree(node_data_path, sub_trees_path) + + def cwl_update_outputs_optional_rosetree(rose_tree: RoseTree) -> RoseTree: """Updates outputs optional for every CWL CommandLineTool