about summary refs log tree commit diff
diff options
context:
space:
mode:
authorArun Isaac2021-11-02 01:17:41 +0530
committerArun Isaac2021-11-02 01:31:56 +0530
commit0b359cf2c32cde81b4311d55273c8f6c14ca6b93 (patch)
tree50def438dc8033db14804f33a2aef7fcffa2d9ec
parent179925c5d88ae38bc28baf470d350461d49ebede (diff)
downloadccwl-0b359cf2c32cde81b4311d55273c8f6c14ca6b93.tar.gz
ccwl-0b359cf2c32cde81b4311d55273c8f6c14ca6b93.tar.lz
ccwl-0b359cf2c32cde81b4311d55273c8f6c14ca6b93.zip
ccwl: Support external CWL workflows.
* ccwl/ccwl.scm: Import (yaml). Export cwl-workflow?, cwl-workflow,
cwl-workflow-file, cwl-workflow-inputs and cwl-workflow-outputs.
(<cwl-workflow>): New type.
(cwl-workflow, function-input-keys, function-outputs,
function-object): New functions.
(command-input-keys, command-object): Delete functions.
(collect-steps): Replace command-object with function-object,
command-input-keys with function-input-keys and command-outputs with
function-outputs.
* ccwl/cwl.scm (workflow->cwl-scm): Handle <cwl-workflow> objects.
* tests/ccwl.scm ("read all forms of inputs and outputs from a CWL
workflow"): New test.
* guix.scm: Import (gnu packages guile-xyz) and (guix utils).
(guile-libyaml): New variable.
(ccwl)[inputs]: Add guile-libyaml.
-rw-r--r--ccwl/ccwl.scm132
-rw-r--r--ccwl/cwl.scm2
-rw-r--r--guix.scm28
-rw-r--r--tests/ccwl.scm14
4 files changed, 139 insertions, 37 deletions
diff --git a/ccwl/ccwl.scm b/ccwl/ccwl.scm
index 626a80e..8faf97e 100644
--- a/ccwl/ccwl.scm
+++ b/ccwl/ccwl.scm
@@ -31,7 +31,7 @@
   #:use-module (srfi srfi-71)
   #:use-module (ice-9 match)
   #:use-module (ccwl utils)
-  #:use-module (ccwl yaml)
+  #:use-module (yaml)
   #:export (command?
             command
             command-inputs
@@ -39,6 +39,11 @@
             command-args
             command-stdin
             command-other
+            cwl-workflow?
+            cwl-workflow
+            cwl-workflow-file
+            cwl-workflow-inputs
+            cwl-workflow-outputs
             workflow
             workflow?
             workflow-steps
@@ -133,6 +138,13 @@
   (stdin command-stdin)
   (other command-other))
 
+(define-immutable-record-type <cwl-workflow>
+  (make-cwl-workflow file inputs outputs)
+  cwl-workflow?
+  (file cwl-workflow-file)
+  (inputs cwl-workflow-inputs)
+  (outputs cwl-workflow-outputs))
+
 (define-immutable-record-type <workflow>
   (make-workflow steps inputs outputs other)
   workflow?
@@ -213,13 +225,61 @@ RUN-ARGS. If such an input is not present in RUN-ARGS, return #f."
                    (list #,@other)))
               #'(args ...))))))
 
+(define (cwl-workflow file)
+  (define (parameters->id+type parameters)
+    (if (vector? parameters)
+        ;; Vector of dictionaries
+        (map (lambda (alist)
+               (cons (string->symbol (assoc-ref alist "id"))
+                     (string->symbol (assoc-ref alist "type"))))
+             (vector->list parameters))
+        ;; One dictionary
+        (map (match-lambda
+               ((id . (? string? type))
+                (cons (string->symbol id)
+                      (string->symbol type)))
+               ((id . alist)
+                (cons (string->symbol id)
+                      (string->symbol (assoc-ref alist "type")))))
+             parameters)))
+
+  (unless (file-exists? file)
+    (error "CWL workflow file does not exist" file))
+  ;; Read inputs/outputs from CWL workflow YAML file and build a
+  ;; <cwl-workflow> object.
+  (let ((yaml (read-yaml-file file)))
+    (make-cwl-workflow file
+                       (map (match-lambda
+                              ((id . type)
+                               (make-input id type #f #f #f #f #f)))
+                            (parameters->id+type (assoc-ref yaml "inputs")))
+                       (map (match-lambda
+                              ((id . type)
+                               (make-output id type #f #f #f)))
+                            (parameters->id+type (assoc-ref yaml "outputs"))))))
+
 (define (input=? input1 input2)
   (eq? (input-id input1)
        (input-id input2)))
 
-(define (command-input-keys command)
-  "Return the list of input keys accepted by COMMAND."
-  (map input-id (command-inputs command)))
+(define (function-input-keys function)
+  "Return the list of input keys accepted by FUNCTION, a <command>
+object or a <cwl-workflow> object."
+  (map input-id
+       ((cond
+         ((command? function) command-inputs)
+         ((cwl-workflow? function) cwl-workflow-inputs)
+         (else (error "Unrecognized ccwl function" function)))
+        function)))
+
+(define (function-outputs function)
+  "Return the outputs of FUNCTION, a <command> or <cwl-workflow>
+object."
+  ((cond
+    ((command? function) command-outputs)
+    ((cwl-workflow? function) cwl-workflow-outputs)
+    (else (error "Unrecognized ccwl function" function)))
+   function))
 
 (define-immutable-record-type <key>
   (make-key name cwl-id step)
@@ -241,13 +301,15 @@ RUN-ARGS. If such an input is not present in RUN-ARGS, return #f."
       ;; Global input/output
       (symbol->string (key-cwl-id key))))
 
-(define (command-object command-syntax)
-  "Return the command object described by COMMAND-SYNTAX. If such a
-command is not defined, return #f."
+(define (function-object x)
+  "Return the ccwl function object (a <command> or a <cwl-workflow>
+object) described by syntax X. If such a ccwl function is not defined,
+return #f."
   (let ((var (module-variable (current-module)
-                              (syntax->datum command-syntax))))
+                              (syntax->datum x))))
     (and var
-         (command? (variable-ref var))
+         (or (command? (variable-ref var))
+             (cwl-workflow? (variable-ref var)))
          (variable-ref var))))
 
 (define (collect-steps x input-keys)
@@ -279,35 +341,35 @@ represented by <step> objects."
                         key))
                   input-keys)
              (list)))
-    ;; commands with only a single input when only a single key is
-    ;; available at this step and when no inputs are passed to it
-    ((command (step-id))
-     (and (command-object #'command)
+    ;; ccwl functions with only a single input when only a single key
+    ;; is available at this step and when no inputs are passed to it
+    ((function (step-id))
+     (and (function-object #'function)
           (= (length input-keys) 1)
-          (= (length (command-input-keys
-                      (command-object #'command)))
+          (= (length (function-input-keys
+                      (function-object #'function)))
              1))
-     (collect-steps #`(command (step-id)
-                               #,(match (command-input-keys
-                                         (command-object #'command))
-                                   ((command-key) (symbol->keyword command-key)))
-                               #,(match input-keys
-                                   ((input-key) (key-name input-key))))
+     (collect-steps #`(function (step-id)
+                                #,(match (function-input-keys
+                                          (function-object #'function))
+                                    ((command-key) (symbol->keyword command-key)))
+                                #,(match input-keys
+                                    ((input-key) (key-name input-key))))
                     input-keys))
-    ((command (step-id) args ...)
+    ((function (step-id) args ...)
      ;; Run a whole bunch of tests so that we can produce useful error
      ;; messages.
      (let ((input-key-symbols (map key-name input-keys))
-           (command-object (command-object #'command))
+           (function-object (function-object #'function))
            (step-id (syntax->datum #'step-id)))
        ;; Test for undefined command.
-       (unless command-object
-         (error "Undefined ccwl command:" (syntax->datum #'command)))
+       (unless function-object
+         (error "Undefined ccwl command:" (syntax->datum #'function)))
        ;; Test for missing required parameters.
        ;; TODO: Filter out optional parameters.
        (match (lset-difference
                eq?
-               (command-input-keys command-object)
+               (function-input-keys function-object)
                (map (match-lambda
                       ((key . _) (keyword->symbol key)))
                     (syntax->datum (pairify #'(args ...)))))
@@ -322,13 +384,13 @@ represented by <step> objects."
        (for-each (match-lambda
                    ((arg . value)
                     (unless (memq (keyword->symbol arg)
-                                  (command-input-keys command-object))
+                                  (function-input-keys function-object))
                       (scm-error 'misc-error
                                  #f
                                  "ccwl command `~S' does not accept input key `~S'. Accepted keys are `~S'."
-                                 (list (syntax->datum #'command)
+                                 (list (syntax->datum #'ccwl-function)
                                        arg
-                                       (command-input-keys command-object))
+                                       (function-input-keys function-object))
                                  #f))
                     (unless (memq value input-key-symbols)
                       (scm-error 'misc-error
@@ -340,9 +402,9 @@ represented by <step> objects."
        (values (append (remove key-step input-keys)
                        (map (lambda (output)
                               (key (output-id output) step-id))
-                            (command-outputs command-object)))
+                            (function-outputs function-object)))
                (list (make-step step-id
-                                command-object
+                                function-object
                                 (map (match-lambda
                                        ((arg . value)
                                         (cons (keyword->symbol arg)
@@ -351,10 +413,10 @@ represented by <step> objects."
                                                        (eq? value (key-name key)))
                                                      input-keys)))))
                                      (pairify (syntax->datum #'(args ...))))
-                                (command-outputs command-object))))))
-    ;; commands with an implicit step identifier
-    ((command args ...)
-     (collect-steps #'(command (command) args ...)
+                                (function-outputs function-object))))))
+    ;; ccwl functions with an implicit step identifier
+    ((function args ...)
+     (collect-steps #'(function (function) args ...)
                     input-keys))
     ;; any other unrecognized syntax
     (x (error "Unrecognized syntax:" (syntax->datum #'x)))))
diff --git a/ccwl/cwl.scm b/ccwl/cwl.scm
index 47b30da..e4d03bc 100644
--- a/ccwl/cwl.scm
+++ b/ccwl/cwl.scm
@@ -86,6 +86,8 @@ association list."
                        (run . ,(match (step-run step)
                                  ((? command? command)
                                   (command->cwl-scm command))
+                                 ((? cwl-workflow? cwl-workflow)
+                                  (cwl-workflow-file cwl-workflow))
                                  (tree tree)))))
                    (workflow-steps workflow)))))
 
diff --git a/guix.scm b/guix.scm
index 9339da0..78d7c41 100644
--- a/guix.scm
+++ b/guix.scm
@@ -31,6 +31,7 @@
              (gnu packages bioinformatics)
              (gnu packages graphviz)
              (gnu packages guile)
+             ((gnu packages guile-xyz) #:prefix guix:)
              (gnu packages pkg-config)
              (gnu packages skribilo)
              (gnu packages texinfo)
@@ -38,10 +39,32 @@
              (guix gexp)
              (guix git-download)
              (guix packages)
-             ((guix licenses) #:prefix license:))
+             ((guix licenses) #:prefix license:)
+             (guix utils))
 
 (define %source-dir (dirname (current-filename)))
 
+;; The upstream Guix guile-libyaml package is broken. Fix it
+;; temporarily here.
+(define guile-libyaml
+  (package
+    (inherit guix:guile-libyaml)
+    (arguments
+     (substitute-keyword-arguments (package-arguments guix:guile-libyaml)
+       ((#:phases phases)
+        `(modify-phases ,phases
+           (replace 'remove-unused-files
+             (lambda _
+               (for-each delete-file
+                         (list "guix.scm" "demo1.scm" "demo1.yml"))))
+           (replace 'build-ffi
+             (lambda* (#:key inputs #:allow-other-keys)
+               (substitute* "yaml/libyaml.scm"
+                 (("dynamic-link \"libyaml\"")
+                  (string-append "dynamic-link \""
+                                 (assoc-ref inputs "libyaml")
+                                 "/lib/libyaml\"")))))))))))
+
 (define ccwl
   (package
     (name "ccwl")
@@ -53,7 +76,8 @@
     (arguments
      '(#:make-flags '("GUILE_AUTO_COMPILE=0"))) ; to prevent guild warnings
     (inputs
-     `(("guile" ,guile-3.0)))
+     `(("guile" ,guile-3.0)
+       ("guile-libyaml" ,guile-libyaml)))
     (native-inputs
      `(("autoconf" ,autoconf)
        ("automake" ,automake)
diff --git a/tests/ccwl.scm b/tests/ccwl.scm
index 8f09fea..5d8a17f 100644
--- a/tests/ccwl.scm
+++ b/tests/ccwl.scm
@@ -34,4 +34,18 @@
          'file)
         'inputBinding)))
 
+(test-equal "read all forms of inputs and outputs from a CWL workflow"
+  '(((spam string))
+    ((ham stdout)
+     (eggs stdout)))
+  (let ((cwl-workflow (cwl-workflow "tests/input-output-parameters.cwl")))
+    (list (map (lambda (input)
+                 (list (input-id input)
+                       (input-type input)))
+               (cwl-workflow-inputs cwl-workflow))
+          (map (lambda (output)
+                 (list (output-id output)
+                       (output-type output)))
+               (cwl-workflow-outputs cwl-workflow)))))
+
 (test-end "ccwl")