summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ravanan/command-line-tool.scm187
1 files changed, 143 insertions, 44 deletions
diff --git a/ravanan/command-line-tool.scm b/ravanan/command-line-tool.scm
index 8815211..6711450 100644
--- a/ravanan/command-line-tool.scm
+++ b/ravanan/command-line-tool.scm
@@ -375,8 +375,50 @@ The returned @code{File} type objects are updated with
 store-interned paths in the @code{location} and @code{path}
 fields. The @code{basename} field contains the basename of the
 original path, and not the store-interned path."
-  (define (resolve inputs types)
-    (vector-map (lambda (input type-tree)
+  (define (canonicalize-file-input input)
+    "Canonicalize @code{File} type @var{input} and its secondary files."
+    (let* ((path (or (and (assoc-ref input "location")
+                          (location->path (assoc-ref input "location")))
+                     (assoc-ref input "path")))
+           (interned-path (intern-file path store)))
+      (maybe-assoc-set input
+        (cons "location" (just interned-path))
+        (cons "path" (just interned-path))
+        (cons "basename" (just (basename path)))
+        (cons "checksum" (just (checksum path)))
+        (cons "size" (just (stat:size (stat path))))
+        (cons "secondaryFiles"
+              (maybe-let* ((secondary-files (maybe-assoc-ref (just input)
+                                                             "secondaryFiles")))
+                (just (vector-map canonicalize-file-input
+                                  secondary-files)))))))
+
+  (define (match-secondary-file-pattern input pattern)
+    "Return @code{#t} if secondary file @var{pattern} matches at least one secondary
+file in @var{input}."
+    ;; TODO: Implement caret characters in SecondaryFileSchema DSL.
+    (vector-any (lambda (secondary-file)
+                  (string=? (assoc-ref* secondary-file "path")
+                            (string-append (assoc-ref* input "path")
+                                           pattern)))
+                (or (assoc-ref input "secondaryFiles")
+                    (user-error "Missing secondaryFiles in input ~a"
+                                input))))
+
+  (define (check-secondary-files input secondary-files)
+    "Check if all required @var{secondary-files} are present in @var{input}. If not,
+error out."
+    (vector-for-each (lambda (secondary-file)
+                       (let ((pattern (assoc-ref* secondary-file "pattern")))
+                         (when (and (assoc-ref* secondary-file "required")
+                                    (not (match-secondary-file-pattern input pattern)))
+                           (user-error "Secondary file ~a missing in input ~a"
+                                       pattern
+                                       input))))
+                     secondary-files))
+
+  (define (resolve inputs types maybe-secondary-files)
+    (vector-map (lambda (input type-tree maybe-secondary-files)
                   ;; Check type.
                   (let* ((type (formal-parameter-type type-tree))
                          (matched-type (match-type input type)))
@@ -388,24 +430,21 @@ original path, and not the store-interned path."
                      ((array-type? matched-type)
                       (resolve input
                                (make-vector (vector-length input)
-                                            (assoc-ref type-tree "items"))))
-                     ;; Intern File type inputs and fully resolve
-                     ;; them.
+                                            (assoc-ref type-tree "items"))
+                               maybe-secondary-files))
+                     ;; Intern File type inputs and fully resolve them.
                      ((eq? matched-type 'File)
-                      (let* ((path (or (and (assoc-ref input "location")
-                                            (location->path (assoc-ref input "location")))
-                                       (assoc-ref input "path")))
-                             (interned-path (intern-file path store)))
-                        (assoc-set input
-                                   (cons "location" interned-path)
-                                   (cons "path" interned-path)
-                                   (cons "basename" (basename path))
-                                   (cons "checksum" (checksum path))
-                                   (cons "size" (stat:size (stat path))))))
+                      (let ((resolved-input (canonicalize-file-input input)))
+                        ;; Ensure secondary files are provided with File type
+                        ;; inputs.
+                        (maybe-bind maybe-secondary-files
+                                    (cut check-secondary-files resolved-input <>))
+                        resolved-input))
                      ;; Return other input types unchanged.
                      (else input))))
                 inputs
-                types))
+                types
+                maybe-secondary-files))
   
   (vector-map->list (lambda (input formal-input)
                       (cons (assoc-ref formal-input "id")
@@ -421,6 +460,10 @@ original path, and not the store-interned path."
                                              (or (assoc-ref formal-input "type")
                                                  (user-error "Type of input ~a not specified"
                                                              id))))
+                                         formal-inputs)
+                             (vector-map (lambda (formal-input)
+                                           (maybe-assoc-ref (just formal-input)
+                                                            "secondaryFiles"))
                                          formal-inputs))
                     formal-inputs))
 
@@ -636,32 +679,43 @@ path. Return the interned path."
         interned-path)))
 
 (define (copy-input-files-gexp inputs)
-  "Return a G-expression that copies @code{File} type inputs from @var{inputs} into
-@code{inputs-directory} and return a new association list with updated
-@code{location} and @code{path} fields.
+  "Return a G-expression that copies @code{File} type inputs (along with secondary
+files) from @var{inputs} into @code{inputs-directory} and return a new
+association list with updated @code{location} and @code{path} fields.
 
 The returned G-expression will reference an @code{inputs-directory} variable."
+  (define (copy-input-files input)
+    (cond
+     ((vector? input)
+      #~,(list->vector
+          `#$(map copy-input-files
+                  (vector->list input))))
+     ((eq? (object-type input)
+           'File)
+      #~,(let ((path-in-inputs-directory
+                (expand-file-name #$(assoc-ref input "basename")
+                                  inputs-directory)))
+           (copy-file #$(assoc-ref input "location")
+                      path-in-inputs-directory)
+           (maybe-assoc-set '#$input
+             (cons "location"
+                   (just path-in-inputs-directory))
+             (cons "path"
+                   (just path-in-inputs-directory))
+             (cons "secondaryFiles"
+                   #$(from-maybe
+                      (maybe-let* ((secondary-files
+                                    (maybe-assoc-ref (just input) "secondaryFiles")))
+                        (just #~(just (list->vector
+                                       `#$(vector-map->list copy-input-files
+                                                            secondary-files)))))
+                      #~%nothing)))))
+     (else input)))
+
   #~(list->dotted-list
      `#$(map (match-lambda
                ((id . input)
-                (list id
-                      (let copy-input-files ((input input))
-                        (cond
-                         ((vector? input)
-                          #~,(list->vector
-                              `#$(map copy-input-files
-                                      (vector->list input))))
-                         ((eq? (object-type input)
-                               'File)
-                          #~,(let ((path-in-inputs-directory
-                                    (expand-file-name #$(assoc-ref input "basename")
-                                                      inputs-directory)))
-                               (copy-file #$(assoc-ref input "location")
-                                          path-in-inputs-directory)
-                               (assoc-set '#$input
-                                          (cons "location" path-in-inputs-directory)
-                                          (cons "path" path-in-inputs-directory))))
-                         (else input))))))
+                (list id (copy-input-files input))))
              inputs)))
 
 (define (build-command-line-tool-script name manifest cwl inputs
@@ -785,6 +839,12 @@ named @var{name} with @var{inputs} using tools from Guix manifest
                                             other-outputs)
                                     '#$(map (cut assoc-ref <> "type")
                                             other-outputs)
+                                    (list #$@(map (lambda (output)
+                                                    (match (assoc "secondaryFiles" output)
+                                                      ((_ . secondary-files)
+                                                       #~(just #$secondary-files))
+                                                      (#f #~%nothing)))
+                                                  other-outputs))
                                     (list #$@(map (compose coerce-expression
                                                            output-binding-glob)
                                                   other-outputs))))))
@@ -803,7 +863,9 @@ named @var{name} with @var{inputs} using tools from Guix manifest
                                               (or (assoc-ref cwl "hints")
                                                   #()))))
       (with-imported-modules (source-module-closure '((ravanan work command-line-tool)
+                                                      (ravanan work monads)
                                                       (ravanan work ui)
+                                                      (ravanan work vectors)
                                                       (ravanan glob)
                                                       (guix search-paths))
                                                     #:select? (match-lambda
@@ -814,8 +876,10 @@ named @var{name} with @var{inputs} using tools from Guix manifest
         (with-extensions (list guile-filesystem guile-gcrypt)
           #~(begin
               (use-modules (ravanan work command-line-tool)
+                           (ravanan work monads)
                            (ravanan work ui)
                            (ravanan work utils)
+                           (ravanan work vectors)
                            (ravanan glob)
                            (rnrs io ports)
                            (srfi srfi-1)
@@ -843,10 +907,39 @@ named @var{name} with @var{inputs} using tools from Guix manifest
                     (cons "size" (stat:size (stat path)))
                     (cons "checksum" (checksum path)))))
 
-              (define (path->value path workflow-output-directory)
-                (canonicalize-file-value `(("class" . "File")
-                                           ("path" . ,path))
-                                         workflow-output-directory))
+              (define (capture-secondary-file path secondary-file
+                                              workflow-output-directory)
+                "Capture @var{secondary-file} for primary @var{path} and return its
+canonicalized value. If @var{required} is @code{#t} and no such secondary file
+is found, error out. @var{workflow-output-directory} is path to the output
+directory of the workflow."
+                (let* ((pattern (assoc-ref* secondary-file "pattern"))
+                       ;; TODO: Implement caret characters in
+                       ;; SecondaryFileSchema DSL.
+                       (secondary-file-path (string-append path pattern))
+                       (secondary-file-value
+                        (and (file-exists? secondary-file-path)
+                             (canonicalize-file-value `(("class" . "File")
+                                                        ("path" . ,secondary-file-path))
+                                                      workflow-output-directory))))
+                  (if (and (assoc-ref* secondary-file "required")
+                           (not secondary-file-value))
+                      (user-error "Secondary file ~a missing for output path ~a"
+                                  pattern
+                                  path)
+                      secondary-file-value)))
+
+              (define (path->value path workflow-output-directory maybe-secondary-files)
+                (maybe-assoc-set (canonicalize-file-value `(("class" . "File")
+                                                            ("path" . ,path))
+                                                          workflow-output-directory)
+                  (cons "secondaryFiles"
+                        (maybe-let* ((secondary-files maybe-secondary-files))
+                          (just (vector-filter-map (cut capture-secondary-file
+                                                        path
+                                                        <>
+                                                        workflow-output-directory)
+                                                   secondary-files))))))
 
               (define (stdout-output->value workflow-output-directory
                                             stdout-directory
@@ -865,10 +958,12 @@ named @var{name} with @var{inputs} using tools from Guix manifest
                              hashed-filename)
                            ;; Else, return the stdout filename as it is.
                            #$stdout-filename)
-                       workflow-output-directory)))
+                       workflow-output-directory
+                       %nothing)))
 
               (define (other-output->value workflow-output-directory
-                                           output-id output-type-tree glob-pattern)
+                                           output-id output-type-tree
+                                           maybe-secondary-files glob-pattern)
                 (cons output-id
                       ;; TODO: Support all types.
                       (let* ((output-type (formal-parameter-type output-type-tree))
@@ -879,7 +974,10 @@ named @var{name} with @var{inputs} using tools from Guix manifest
                                       output-type
                                       paths))
                         ;; Coerce output value into matched type.
-                        (let ((output-values (map (cut path->value <> workflow-output-directory)
+                        (let ((output-values (map (cut path->value
+                                                       <>
+                                                       workflow-output-directory
+                                                       maybe-secondary-files)
                                                   paths)))
                           (cond
                            ((memq matched-type (list 'File 'Directory))
@@ -910,6 +1008,7 @@ named @var{name} with @var{inputs} using tools from Guix manifest
               (call-with-temporary-directory
                (lambda (inputs-directory)
                  (let ((inputs
+                        ;; TODO: Update paths of staged files.
                         #$(copy-input-files-gexp
                            (resolve-inputs inputs (assoc-ref* cwl "inputs") store)))
                        (runtime `(("cores" . ,(total-processor-count)))))