guix-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[no subject]


From: Mathieu Othacehe
Date: Fri, 5 Mar 2021 03:09:50 -0500 (EST)

branch: master
commit 325edf2ffa3cfca35cbedbcabd88b778374808fb
Author: Mathieu Othacehe <othacehe@gnu.org>
AuthorDate: Wed Mar 3 15:25:04 2021 +0100

    Rewrite evaluation.
---
 Makefile.am                   |   1 +
 bin/cuirass.in                |  10 +-
 bin/evaluate.in               | 220 +++++++++++++-----------------------
 src/cuirass/base.scm          | 252 ++++++++++--------------------------------
 src/cuirass/database.scm      | 179 ++++++++++++------------------
 src/cuirass/http.scm          |  67 ++++++-----
 src/cuirass/metrics.scm       |   3 +-
 src/cuirass/notification.scm  | 118 +++++++++++++-------
 src/cuirass/specification.scm | 153 +++++++++++++++++++++++++
 src/cuirass/templates.scm     | 123 +++++++++++++--------
 src/schema.sql                |  34 ++----
 tests/database.scm            | 113 ++++++++++---------
 tests/http.scm                |  77 +++++++------
 tests/metrics.scm             |   7 +-
 14 files changed, 663 insertions(+), 694 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 06f0f5f..928d201 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -63,6 +63,7 @@ dist_pkgmodule_DATA =                         \
   src/cuirass/remote-worker.scm                        \
   src/cuirass/rss.scm                          \
   src/cuirass/send-events.scm                  \
+  src/cuirass/specification.scm                        \
   src/cuirass/ui.scm                           \
   src/cuirass/utils.scm                                \
   src/cuirass/templates.scm                     \
diff --git a/bin/cuirass.in b/bin/cuirass.in
index 8dbb14f..e6f7564 100644
--- a/bin/cuirass.in
+++ b/bin/cuirass.in
@@ -33,6 +33,7 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
              (cuirass logging)
              (cuirass metrics)
              (cuirass notification)
+             (cuirass specification)
              (cuirass utils)
              (cuirass watchdog)
              (cuirass zabbix)
@@ -149,13 +150,8 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" 
"$@"
              (with-database
                  (with-notification
                   (and specfile
-                       (let ((new-specs (save-module-excursion
-                                         (lambda ()
-                                           (set-current-module
-                                            (make-user-module '()))
-                                           (primitive-load specfile)))))
-
-                         (for-each db-add-specification new-specs)))
+                       (for-each db-add-specification
+                                 (read-specifications specfile)))
                   (and paramfile (read-parameters paramfile))
 
                   (if one-shot?
diff --git a/bin/evaluate.in b/bin/evaluate.in
index 19d0f12..aa87ddc 100644
--- a/bin/evaluate.in
+++ b/bin/evaluate.in
@@ -6,7 +6,7 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
 ;;;; evaluate -- convert a specification to a job list
 ;;; Copyright © 2016, 2018 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016, 2017 Mathieu Lirzin <mthl@gnu.org>
-;;; Copyright © 2017, 2018 Mathieu Othacehe <m.othacehe@gmail.com>
+;;; Copyright © 2017, 2018, 2021 Mathieu Othacehe <m.othacehe@gmail.com>
 ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
 ;;;
 ;;; This file is part of Cuirass.
@@ -25,148 +25,86 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" 
"$@"
 ;;; along with Cuirass.  If not, see <http://www.gnu.org/licenses/>.
 
 
-;; Note: Do not use any Guix modules (see below).
-(use-modules (ice-9 match)
+(use-modules (cuirass database)
+             (cuirass specification)
+             (guix channels)
+             (guix inferior)
+             (guix licenses)
+             (guix store)
+             (guix utils)
+             (ice-9 match)
              (ice-9 pretty-print)
-             (srfi srfi-1)
-             (srfi srfi-26))
-
-(define (ref module name)
-  "Dynamically link variable NAME under MODULE and return it."
-  (let ((m (resolve-interface module)))
-    (module-ref m name)))
-
-(define (absolutize directory load-path)
-  (if (string-prefix? "/" load-path)
-      load-path
-      (string-append directory "/" load-path)))
-
-(define (input-checkout checkouts input-name)
-  "Find in CHECKOUTS the CHECKOUT corresponding to INPUT-NAME, and return it."
-  (find (lambda (checkout)
-          (string=? (assq-ref checkout #:input)
-                    input-name))
-        checkouts))
-
-(define (spec-source spec checkouts)
-  "Find in CHECKOUTS the directory where the #:PROC-INPUT repository of SPEC
-has been checked out, and return it."
-  (let* ((input-name (assq-ref spec #:proc-input))
-         (checkout (input-checkout checkouts input-name)))
-    (assq-ref checkout #:directory)))
-
-(define (spec-load-path spec checkouts)
-  "Find in CHECKOUTS the load paths of each SPEC's #:LOAD-PATH-INPUTS and
-return them as a list."
-  (map (lambda (input-name)
-         (let* ((checkout (input-checkout checkouts input-name))
-                (directory (assq-ref checkout #:directory))
-                (load-path (assq-ref checkout #:load-path)))
-           (absolutize directory load-path)))
-       (assq-ref spec #:load-path-inputs)))
-
-(define (spec-package-path spec checkouts)
-  "Find in CHECKOUTS the package paths of each SPEC's #:PACKAGE-PATH-INPUTS
-and return them as a colon separated string."
-  (let* ((input-names (assq-ref spec #:package-path-inputs))
-         (checkouts (map (cut input-checkout checkouts <>) input-names)))
-    (string-join
-     (map
-      (lambda (checkout)
-        (let ((directory (assq-ref checkout #:directory))
-              (load-path (assq-ref checkout #:load-path)))
-          (absolutize directory load-path)))
-      checkouts)
-     ":")))
-
-(define (format-checkouts checkouts)
-  "Format checkouts the way Hydra does: #:NAME becomes the key as a symbol,
-#:DIRECTORY becomes FILE-NAME and #:COMMIT becomes REVISION.  The other
-entries are added because they could be useful during the evaluation."
-  (map
-   (lambda (checkout)
-     (let loop ((in checkout)
-                (out '())
-                (name #f))
-       (match in
-         (()
-          (cons name out))
-         (((#:input . val) . rest)
-          (loop rest out (string->symbol val)))
-         (((#:directory . val) . rest)
-          (loop rest (cons `(file-name . ,val) out) name))
-         (((#:commit . val) . rest)
-          (loop rest (cons `(revision . ,val) out) name))
-         (((keyword . val) . rest)
-          (loop rest (cons `(,(keyword->symbol keyword) . ,val) out) name)))))
-   checkouts))
+             (ice-9 threads))
+
+(define (checkouts->channel-instances checkouts)
+  "Return the list of CHANNEL-INSTANCE records describing the given
+CHECKOUTS."
+  (map (lambda (checkout)
+         (let ((channel (assq-ref checkout #:channel))
+               (directory (assq-ref checkout #:directory))
+               (commit (assq-ref checkout #:commit)))
+           (checkout->channel-instance directory
+                                       #:name channel
+                                       #:commit commit)))
+       checkouts))
+
+(define* (inferior-evaluation store instances
+                              #:key
+                              eval-id spec build systems)
+  "Spawn an inferior on INSTANCES that uses the given STORE.  Withing that
+inferior, call PROC with PROC-ARGS arguments from MODULE.  Pass PROC a
+register procedure that writes its arguments on a socket.  Listen that socket
+for new jobs and register them using REGISTER-JOB procedure."
+  ;; The module where the below procedure is defined.
+  (define eval-module '(gnu ci))
+
+  ;; The Guix procedure for job evaluation.
+  (define eval-proc 'cuirass-jobs)
+
+  (let* ((cached (cached-channel-instance store instances))
+         (inferior (open-inferior cached))
+         (channels (map channel-instance->sexp instances))
+         (args `((channels . ,channels)
+                 (systems . ,systems)
+                 (subset . ,build))))
+    (inferior-eval `(use-modules ,eval-module) inferior)
+    (let ((jobs
+           (inferior-eval-with-store
+            inferior store
+            `(lambda (store)
+               (,eval-proc store ',args)))))
+      (db-register-builds jobs eval-id spec))))
 
 (define* (main #:optional (args (command-line)))
+  "This procedure spawns an inferior on the given channels.  An evaluation
+procedure is called within that inferior.  The evaluation procedure is passed
+a job registration callback.  The registered jobs are then read on a socket
+nd registered in database."
   (match args
-    ((command spec-str checkouts-str)
-     ;; Load FILE, a Scheme file that defines Hydra jobs.
-     ;;
-     ;; Until FILE is loaded, we must *not* load any Guix module because the
-     ;; user may be providing its own with #:LOAD-PATH-INPUTS, which could
-     ;; differ from ours.  The 'ref' procedure helps us achieve this.
-     (let* ((%user-module (make-fresh-user-module))
-            (spec (with-input-from-string spec-str read))
-            (checkouts (with-input-from-string checkouts-str read))
-            (source (spec-source spec checkouts))
-            (file (assq-ref spec #:proc-file))
-            (stdout (current-output-port))
-            (stderr (current-error-port)))
-       (setenv "GUIX_PACKAGE_PATH" (spec-package-path spec checkouts))
-
-       ;; Since we have relative file name canonicalization by default, better
-       ;; change to SOURCE to make sure things like 'include' with relative
-       ;; file names work as expected.
-       (chdir source)
-
-       ;; Change '%load-path' once and for all.  We need it to be effective
-       ;; both when we load FILE and when we later call the thunks.
-       (set! %load-path (append (spec-load-path spec checkouts) %load-path))
-
-       (save-module-excursion
-        (lambda ()
-          (set-current-module %user-module)
-          (primitive-load file)))
-
-       ;; From there on we can access Guix modules.
-
-       (let ((store ((ref '(guix store) 'open-connection)))
-             (set-build-options (ref '(guix store)
-                                     'set-build-options)))
-         (unless (assoc-ref spec #:use-substitutes?)
-           ;; Make sure we don't resort to substitutes.
-           (set-build-options store #:use-substitutes? #f #:substitute-urls 
'()))
-
-         ;; Grafts can trigger early builds.  We do not want that to happen
-         ;; during evaluation, so use a sledgehammer to catch such problems.
-         ;; An exception, though, is the evaluation of Guix itself, which
-         ;; requires building a "trampoline" program.
-         (let ((real-build-things (ref '(guix store) 'build-things)))
-           (module-set! (resolve-module '(guix store))
-                        'build-things
-                        (lambda (store . args)
-                          (simple-format stderr "warning:
-building things during evaluation~%")
-                          (simple-format stderr
-                                         "'build-things' arguments: ~S~%"
-                                         args)
-                          (apply real-build-things store args))))
-
-         ;; Call the entry point of FILE and print the resulting job sexp.
-         (let* ((proc (module-ref %user-module (assq-ref spec #:proc)))
-                (args `(,@(format-checkouts checkouts)
-                        ,@(or (assq-ref spec #:proc-args) '())))
-                (thunks (proc store args)))
-           (pretty-print
-            `(evaluation ,(map (lambda (thunk) (thunk))
-                               thunks))
-            stdout)))))
-    ((command _ ...)
-     (simple-format (current-error-port) "Usage: ~A FILE
-Evaluate the Hydra jobs defined in FILE.~%"
-                    command)
+    ((command database eval-str)
+     (parameterize ((%package-database database))
+       (with-database
+           (let* ((eval-id (with-input-from-string eval-str read))
+                  (name (db-get-evaluation-specification eval-id))
+                  (spec (db-get-specification name))
+                  (checkouts (db-get-checkouts eval-id))
+                  (instances (checkouts->channel-instances checkouts))
+                  (build (specification-build spec))
+                  (systems (specification-systems spec)))
+
+             (par-for-each
+              (lambda (system)
+                (with-store store
+                  (set-build-options store
+                                       #:use-substitutes? #f
+                                       #:substitute-urls '())
+                  (inferior-evaluation store instances
+                                       #:eval-id eval-id
+                                       #:spec spec
+                                       #:build build
+                                       #:systems (list system))))
+              systems)
+             (display 'done)))))
+    (x
+     (format (current-error-port) "Wrong command: ~a~%." x)
      (exit 1))))
diff --git a/src/cuirass/base.scm b/src/cuirass/base.scm
index 8528409..6c09783 100644
--- a/src/cuirass/base.scm
+++ b/src/cuirass/base.scm
@@ -1,7 +1,7 @@
 ;;; base.scm -- Cuirass base module
 ;;; Copyright © 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
 ;;; Copyright © 2016, 2017 Mathieu Lirzin <mthl@gnu.org>
-;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
+;;; Copyright © 2017, 2020, 2021 Mathieu Othacehe <m.othacehe@gmail.com>
 ;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
 ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
 ;;;
@@ -26,10 +26,12 @@
   #:use-module (cuirass logging)
   #:use-module (cuirass database)
   #:use-module (cuirass remote)
+  #:use-module (cuirass specification)
   #:use-module (cuirass utils)
   #:use-module ((cuirass config) #:select (%localstatedir))
   #:use-module (gnu packages)
   #:use-module (guix build utils)
+  #:use-module (guix channels)
   #:use-module (guix derivations)
   #:use-module (guix store)
   #:use-module (guix ui)
@@ -62,9 +64,6 @@
   #:export (;; Procedures.
             call-with-time-display
             read-parameters
-            fetch-input
-            fetch-inputs
-            compile
             evaluate
             build-derivations&
             set-build-successful!
@@ -198,86 +197,6 @@ values."
     (lambda (key err)
       (report-git-error err))))
 
-(define* (fetch-input store input #:key writable-copy?) ;TODO fix desc
-  "Get the latest version of repository inputified in INPUT.  Return an
-association list containing the input name, the content of the git repository
-at URL copied into a store directory and the sha1 of the top level commit in
-this directory.
-
-When WRITABLE-COPY? is true, return a writable copy; otherwise, return a
-read-only directory."
-
-  (define (add-origin branch)
-    "Prefix branch name with origin if no remote is specified."
-    (if (string-index branch #\/)
-        branch
-        (string-append "origin/" branch)))
-
-  (let ((name   (assq-ref input #:name))
-        (url    (assq-ref input #:url))
-        (branch (and=> (assq-ref input #:branch)
-                       (lambda (b)
-                         `(branch . ,(add-origin b)))))
-        (commit (and=> (assq-ref input #:commit)
-                       (lambda (c)
-                         `(commit . ,c))))
-        (tag    (and=> (assq-ref input #:tag)
-                       (lambda (t)
-                         `(tag . ,t)))))
-    (let*-values (((directory commit)
-                   (latest-repository-commit store url
-                                             #:cache-directory
-                                             (%package-cachedir)
-                                             #:ref (or branch commit tag)))
-                  ((timestamp)
-                   (time-second (current-time time-utc))))
-      ;; TODO: When WRITABLE-COPY? is true, we could directly copy the
-      ;; checkout directly in a writable location instead of copying it to the
-      ;; store first.
-      (let ((directory (if writable-copy?
-                           (make-writable-copy directory
-                                               (string-append
-                                                (%package-cachedir) "/" name))
-                           directory)))
-        `((#:input . ,name)
-          (#:directory . ,directory)
-          (#:commit . ,commit)
-          (#:timestamp . ,timestamp)
-          (#:load-path . ,(assq-ref input #:load-path))
-          (#:no-compile? . ,(assq-ref input #:no-compile?)))))))
-
-(define (make-writable-copy source target)
-  "Create TARGET and make it a writable copy of directory SOURCE; delete
-TARGET beforehand if it exists.  Return TARGET."
-  (define (chmod+w file stat _)
-    (chmod file (logior #o200 (stat:perms stat))))
-
-  (mkdir-p (dirname target))
-  ;; Remove any directory with the same name.
-  (false-if-exception (delete-file-recursively target))
-  (copy-recursively source target)
-
-  ;; Make all the files in TARGET writable.
-  (file-system-fold (const #t)                    ;enter?
-                    chmod+w                       ;leaf
-                    chmod+w                       ;down
-                    (const #t)                    ;up
-                    (const #t)                    ;skip
-                    (const #f)                    ;error
-                    *unspecified*                 ;init
-                    target)
-
-  target)
-
-(define (compile dir)
-  ;; Required for fetching Guix bootstrap tarballs.
-  "Compile files in repository in directory DIR."
-  (with-directory-excursion dir
-    (or (file-exists? "configure") (system* "./bootstrap"))
-    (or (file-exists? "Makefile")
-        (system* "./configure" "--localstatedir=/var"))
-    (zero? (system* "make" "-j" (number->string (current-processor-count))))))
-
 (define-condition-type &evaluation-error &error
   evaluation-error?
   (name evaluation-error-spec-name)
@@ -322,7 +241,7 @@ fibers."
                  "/log/cuirass/evaluations/"
                  (number->string eval-id) ".gz"))
 
-(define (evaluate store spec eval-id checkouts)
+(define (evaluate store spec eval-id)
   "Evaluate and build package derivations defined in SPEC, using CHECKOUTS.
 Return a list of jobs that are associated to EVAL-ID."
   (define log-file
@@ -355,8 +274,8 @@ Return a list of jobs that are associated to EVAL-ID."
                 (with-error-to-port (cdr log-pipe)
                   (lambda ()
                     (open-pipe* OPEN_READ "evaluate"
-                                (object->string spec)
-                                (object->string checkouts))))))
+                                (%package-database)
+                                (object->string eval-id))))))
          (result (match (read/non-blocking port)
                    ;; If an error occured during evaluation report it,
                    ;; otherwise, suppose that data read from port are
@@ -367,16 +286,13 @@ Return a list of jobs that are associated to EVAL-ID."
                     (close-port (cdr log-pipe))
                     (raise (condition
                             (&evaluation-error
-                             (name (assq-ref spec #:name))
+                             (name (specification-name spec))
                              (id eval-id)))))
-                   (data data))))
+                   (_ #t))))
     (close-port (cdr log-pipe))
     (close-pipe port)
-    (match result
-      (('evaluation jobs)
-       (let* ((spec-name (assq-ref spec #:name)))
-         (log-message "evaluation ~a for '~a' completed" eval-id spec-name)
-         jobs)))))
+    (let ((spec-name (specification-name spec)))
+      (log-message "evaluation ~a for '~a' completed" eval-id spec-name))))
 
 
 ;;;
@@ -479,7 +395,7 @@ build products."
                       (assq-ref build #:specification)))))
     (when (and spec build)
       (create-build-outputs build
-                            (assq-ref spec #:build-outputs))))
+                            (specification-build-outputs spec))))
   (db-update-build-status! drv (build-status succeeded)
                            #:log-file log))
 
@@ -670,17 +586,17 @@ started)."
         (spawn-builds store valid))
       (log-message "done with restarted builds"))))
 
-(define (create-build-outputs build product-specs)
+(define (create-build-outputs build build-outputs)
   "Given BUILDS a list of built derivations, save the build products described
-by PRODUCT-SPECS."
+by BUILD-OUTPUTS."
   (define (build-has-products? job-regex)
     (let ((job-name (assq-ref build #:job-name)))
       (string-match job-regex job-name)))
 
-  (define* (find-product build spec)
+  (define* (find-product build build-output)
     (let* ((outputs (assq-ref build #:outputs))
-           (output (assq-ref spec #:output))
-           (path (assq-ref spec #:path))
+           (output (build-output-output build-output))
+           (path (build-output-path build-output-path))
            (root (and=> (assoc-ref outputs output)
                         (cut assq-ref <> #:path))))
       (and root
@@ -691,27 +607,28 @@ by PRODUCT-SPECS."
   (define (file-size file)
     (stat:size (stat file)))
 
-  (for-each (lambda (spec)
-              (let ((product (and (build-has-products? (assq-ref spec #:job))
-                                  (find-product build spec))))
+  (for-each (lambda (build-output)
+              (let ((product (and (build-has-products?
+                                   (build-output-job build-output))
+                                  (find-product build build-output))))
                 (when (and product (file-exists? product))
                   (log-message "Adding build product ~a" product)
-                  (db-add-build-product `((#:build . ,(assq-ref build #:id))
-                                          (#:type . ,(assq-ref spec #:type))
-                                          (#:file-size . ,(file-size product))
-                                          ;; TODO: Implement it.
-                                          (#:checksum . "")
-                                          (#:path . ,product))))))
-            product-specs))
-
-(define (build-packages store jobs eval-id)
+                  (db-add-build-product
+                   `((#:build . ,(assq-ref build #:id))
+                     (#:type . ,(build-output-type build-output-type))
+                     (#:file-size . ,(file-size product))
+                     ;; TODO: Implement it.
+                     (#:checksum . "")
+                     (#:path . ,product))))))
+            build-outputs))
+
+(define (build-packages store eval-id)
   "Build JOBS and return a list of Build results."
+  (define builds
+    (db-get-builds `((evaluation . ,eval-id))))
+
   (define derivations
-    (let* ((name (db-get-evaluation-specification eval-id))
-           (specification (db-get-specification name)))
-      (with-time-logging
-       (format #f "evaluation ~a registration" eval-id)
-       (db-register-builds jobs eval-id specification))))
+    (map (cut assq-ref <> #:derivation) builds))
 
   (log-message "evaluation ~a registered ~a new derivations"
                eval-id (length derivations))
@@ -719,22 +636,22 @@ by PRODUCT-SPECS."
                             (evaluation-status succeeded))
 
   (unless (%build-remote?)
-    (spawn-builds store derivations))
-
-  (let* ((results (filter-map (cut db-get-build <>) derivations))
-         (status (map (cut assq-ref <> #:status) results))
-         (success (count (lambda (status)
-                           (= status (build-status succeeded)))
-                         status))
-         (outputs (map (cut assq-ref <> #:outputs) results))
-         (outs (append-map (match-lambda
-                             (((_ (#:path . (? string? outputs))) ...)
-                              outputs))
-                           outputs))
-         (fail (- (length derivations) success)))
-
-    (log-message "outputs:\n~a" (string-join outs "\n"))
-    results))
+    (spawn-builds store derivations)
+
+    (let* ((results (filter-map (cut db-get-build <>) derivations))
+           (status (map (cut assq-ref <> #:status) results))
+           (success (count (lambda (status)
+                             (= status (build-status succeeded)))
+                           status))
+           (outputs (map (cut assq-ref <> #:outputs) results))
+           (outs (append-map (match-lambda
+                               (((_ (#:path . (? string? outputs))) ...)
+                                outputs))
+                             outputs))
+           (fail (- (length derivations) success)))
+
+      (log-message "outputs:\n~a" (string-join outs "\n"))
+      results)))
 
 (define (prepare-git)
   "Prepare Guile-Git's TLS support and all."
@@ -748,68 +665,21 @@ by PRODUCT-SPECS."
      (when (or directory file)
        (set-tls-certificate-locations! directory file)))))
 
-(define (compile? checkout)
-  (not (assq-ref checkout #:no-compile?)))
-
-(define (fetch-inputs spec)
-  "Fetch all inputs of SPEC in parallel."
-  (let* ((inputs (assq-ref spec #:inputs))
-         (thunks
-          (map
-           (lambda (input)
-             (lambda ()
-               (with-store store
-                 (log-message "fetching input '~a' of spec '~a'"
-                              (assq-ref input #:name)
-                              (assq-ref spec #:name))
-                 ;; XXX: Work around: https://issues.guix.gnu.org/44742.
-                 (parameterize ((current-error-port (%make-void-port "rw+")))
-                   (fetch-input store input
-                                #:writable-copy? (compile? input))))))
-           inputs))
-         (results (map %non-blocking thunks)))
-    (map (lambda (checkout)
-           (log-message "fetched input '~a' of spec '~a' (commit ~s)"
-                        (assq-ref checkout #:input)
-                        (assq-ref spec #:name)
-                        (assq-ref checkout #:commit))
-           checkout)
-         results)))
-
-(define (compile-checkouts spec checkouts)
-  "Compile CHECKOUTS in parallel."
-  (let* ((thunks
-          (map
-           (lambda (checkout)
-             (lambda ()
-               (log-message "compiling input '~a' of spec '~a' (commit ~s)"
-                            (assq-ref checkout #:input)
-                            (assq-ref spec #:name)
-                            (assq-ref checkout #:commit))
-               (compile checkout)))
-           checkouts))
-         (results (map %non-blocking thunks)))
-    (map (lambda (checkout)
-           (log-message "compiled input '~a' of spec '~a' (commit ~s)"
-                        (assq-ref checkout #:input)
-                        (assq-ref spec #:name)
-                        (assq-ref checkout #:commit))
-           checkout)
-         results)))
-
 (define (process-specs jobspecs)
   "Evaluate and build JOBSPECS and store results in the database."
   (define (process spec)
     (with-store store
-      (let* ((name (assoc-ref spec #:name))
+      (let* ((name (specification-name spec))
              (timestamp (time-second (current-time time-utc)))
-             (checkouts (fetch-inputs spec))
+             (channels (specification-channels spec))
+             (instances (non-blocking
+                         (latest-channel-instances store channels
+                                                   #:authenticate? #f)))
              (checkouttime (time-second (current-time time-utc)))
-             (eval-id (db-add-evaluation name checkouts
+             (eval-id (db-add-evaluation name instances
                                          #:timestamp timestamp
                                          #:checkouttime checkouttime)))
         (when eval-id
-          (compile-checkouts spec (filter compile? checkouts))
           (spawn-fiber
            (lambda ()
              (guard (c ((evaluation-error? c)
@@ -820,11 +690,9 @@ by PRODUCT-SPECS."
                         #f))
                (log-message "evaluating spec '~a'" name)
                (with-store store
-                 (let ((jobs (evaluate store spec eval-id checkouts)))
-                   (db-set-evaluation-time eval-id)
-                   (log-message "building ~a jobs for '~a'"
-                                (length jobs) name)
-                   (build-packages store jobs eval-id))))))
+                 (evaluate store spec eval-id)
+                 (db-set-evaluation-time eval-id)
+                 (build-packages store eval-id)))))
 
           ;; 'spawn-fiber' returns zero values but we need one.
           *unspecified*))))
@@ -836,6 +704,6 @@ by PRODUCT-SPECS."
                   (process spec))
                 (lambda (key error)
                   (log-message "Git error while fetching inputs of '~a': ~s~%"
-                               (assq-ref spec #:name)
+                               (specification-name spec)
                                (git-error-message error)))))
             jobspecs))
diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm
index cc4ff5a..c882d57 100644
--- a/src/cuirass/database.scm
+++ b/src/cuirass/database.scm
@@ -26,7 +26,9 @@
   #:use-module (cuirass config)
   #:use-module (cuirass notification)
   #:use-module (cuirass remote)
+  #:use-module (cuirass specification)
   #:use-module (cuirass utils)
+  #:use-module (guix channels)
   #:use-module (squee)
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
@@ -46,11 +48,9 @@
             exec-query/bind-params
             expect-one-row
             read-sql-file
-            db-add-input
             db-add-checkout
             db-add-specification
             db-remove-specification
-            db-get-inputs
             db-get-specification
             db-get-specifications
             evaluation-status
@@ -371,99 +371,55 @@ database object."
   "Close database object DB."
   (pg-conn-finish db))
 
-(define (db-add-input spec-name input)
-  (with-db-worker-thread db
-    (exec-query/bind db "\
-INSERT INTO Inputs (specification, name, url, load_path, branch, \
-tag, revision, no_compile_p) VALUES ("
-                     spec-name ", "
-                     (assq-ref input #:name) ", "
-                     (assq-ref input #:url) ", "
-                     (assq-ref input #:load-path) ", "
-                     (assq-ref input #:branch) ", "
-                     (assq-ref input #:tag) ", "
-                     (assq-ref input #:commit) ", "
-                     (if (assq-ref input #:no-compile?) 1 0) ")
-ON CONFLICT ON CONSTRAINT inputs_pkey DO NOTHING;")))
-
-(define (db-add-checkout spec-name eval-id checkout)
-  "Insert CHECKOUT associated with SPEC-NAME and EVAL-ID.  If a checkout with
+(define* (db-add-checkout spec-name eval-id instance
+                          #:key timestamp)
+  "Insert INSTANCE associated with SPEC-NAME and EVAL-ID.  If a checkout with
 the same revision already exists for SPEC-NAME, return #f."
-  (with-db-worker-thread db
-    (match (expect-one-row
-            (exec-query/bind db "\
-INSERT INTO Checkouts (specification, revision, evaluation, input,
+  (let ((channel (channel-instance-channel instance)))
+    (with-db-worker-thread db
+      (match (expect-one-row
+              (exec-query/bind db "\
+INSERT INTO Checkouts (specification, revision, evaluation, channel,
 directory, timestamp) VALUES ("
-                             spec-name ", "
-                             (assq-ref checkout #:commit) ", "
-                             eval-id ", "
-                             (assq-ref checkout #:input) ", "
-                             (assq-ref checkout #:directory) ", "
-                             (or (assq-ref checkout #:timestamp) 0) ")
+                               spec-name ", "
+                               (channel-instance-commit instance) ", "
+                               eval-id ", "
+                               (symbol->string (channel-name channel)) ", "
+                               (channel-instance-checkout instance) ", "
+                               (or timestamp 0) ")
 ON CONFLICT ON CONSTRAINT checkouts_pkey DO NOTHING
 RETURNING (specification, revision);"))
-      (x x)
-      (() #f))))
+        (x x)
+        (() #f)))))
 
 (define (db-add-specification spec)
-  "Store SPEC in database the database.  SPEC inputs are stored in the INPUTS
-table."
+  "Store SPEC in database."
   (with-db-worker-thread db
     (match (expect-one-row
             (exec-query/bind db "\
-INSERT INTO Specifications (name, load_path_inputs, \
-package_path_inputs, proc_input, proc_file, proc, proc_args, \
-build_outputs, notifications, priority) \
+INSERT INTO Specifications (name, build, channels, \
+build_outputs, notifications, priority, systems) \
   VALUES ("
-                             (assq-ref spec #:name) ", "
-                             (assq-ref spec #:load-path-inputs) ", "
-                             (assq-ref spec #:package-path-inputs) ", "
-                             (assq-ref spec #:proc-input) ", "
-                             (assq-ref spec #:proc-file) ", "
-                             (symbol->string (assq-ref spec #:proc)) ", "
-                             (assq-ref spec #:proc-args) ", "
-                             (assq-ref spec #:build-outputs) ", "
-                             (or (assq-ref spec #:notifications) '()) ", "
-                             (or (assq-ref spec #:priority) max-priority) ")
+                             (specification-name spec) ", "
+                             (specification-build spec) ", "
+                             (map channel->sexp
+                                  (specification-channels spec)) ", "
+                             (map build-output->sexp
+                                  (specification-build-outputs spec)) ", "
+                             (map notification->sexp
+                                  (specification-notifications spec)) ", "
+                             (specification-priority spec) ", "
+                             (specification-systems spec) ")
 ON CONFLICT ON CONSTRAINT specifications_pkey DO NOTHING
 RETURNING name;"))
-      ((name)
-       (for-each (lambda (input)
-                   (db-add-input (assq-ref spec #:name) input))
-                 (assq-ref spec #:inputs))
-       name)
+      ((name) name)
       (else #f))))
 
 (define (db-remove-specification name)
-  "Remove the specification matching NAME from the database and its inputs."
+  "Remove the specification matching NAME from the database."
   (with-db-worker-thread db
-    (exec-query db "BEGIN TRANSACTION;")
-    (exec-query/bind db "\
-DELETE FROM Inputs WHERE specification=" name ";")
     (exec-query/bind db "\
-DELETE FROM Specifications WHERE name=" name ";")
-    (exec-query db "COMMIT;")))
-
-(define (db-get-inputs spec-name)
-  (with-db-worker-thread db
-    (let loop ((rows (exec-query/bind
-                      db "SELECT * FROM Inputs WHERE specification="
-                      spec-name "ORDER BY name;"))
-               (inputs '()))
-      (match rows
-        (() (reverse inputs))
-        (((specification name url load-path branch tag revision no-compile-p)
-          . rest)
-         (loop rest
-               (cons `((#:name . ,name)
-                       (#:url . ,url)
-                       (#:load-path . ,load-path)
-                       (#:branch . ,branch)
-                       (#:tag . ,tag)
-                       (#:commit . ,revision)
-                       (#:no-compile? . ,(positive?
-                                          (string->number no-compile-p))))
-                     inputs)))))))
+DELETE FROM Specifications WHERE name=" name ";")))
 
 (define (db-get-specification name)
   "Retrieve a specification in the database with the given NAME."
@@ -474,31 +430,31 @@ DELETE FROM Specifications WHERE name=" name ";")
     (let loop
         ((rows  (if name
                     (exec-query/bind db "
-SELECT * FROM Specifications WHERE name =" name ";")
+SELECT name, build, channels, build_outputs, notifications,\
+priority, systems FROM Specifications WHERE name =" name ";")
                     (exec-query db "
-SELECT * FROM Specifications ORDER BY name ASC;")))
+SELECT name, build, channels, build_outputs, notifications,\
+priority, systems FROM Specifications ORDER BY name ASC;")))
          (specs '()))
       (match rows
         (() (reverse specs))
-        (((name load-path-inputs package-path-inputs proc-input proc-file proc
-                proc-args build-outputs priority notifications)
+        (((name build channels build-outputs notifications priority systems)
           . rest)
          (loop rest
-               (cons `((#:name . ,name)
-                       (#:load-path-inputs .
-                        ,(with-input-from-string load-path-inputs read))
-                       (#:package-path-inputs .
-                        ,(with-input-from-string package-path-inputs read))
-                       (#:proc-input . ,proc-input)
-                       (#:proc-file . ,proc-file)
-                       (#:proc . ,(with-input-from-string proc read))
-                       (#:proc-args . ,(with-input-from-string proc-args read))
-                       (#:inputs . ,(db-get-inputs name))
-                       (#:build-outputs .
-                        ,(with-input-from-string build-outputs read))
-                       (#:notifications .
-                        ,(with-input-from-string notifications read))
-                       (#:priority . ,(string->number priority)))
+               (cons (specification
+                      (name name)
+                      (build (with-input-from-string build read))
+                      (channels
+                       (map sexp->channel
+                            (with-input-from-string channels read)))
+                      (build-outputs
+                       (map sexp->build-output
+                            (with-input-from-string build-outputs read)))
+                      (notifications
+                       (map sexp->notification
+                            (with-input-from-string notifications read)))
+                      (priority (string->number priority))
+                      (systems (with-input-from-string systems read)))
                      specs)))))))
 
 (define-enumeration evaluation-status
@@ -518,12 +474,12 @@ INSERT INTO Events (type, timestamp, event_json) VALUES ("
                        ");")
       #t)))
 
-(define* (db-add-evaluation spec-name checkouts
+(define* (db-add-evaluation spec-name instances
                             #:key
                             (checkouttime 0)
                             (evaltime 0)
                             timestamp)
-  "Add a new evaluation for SPEC-NAME only if one of the CHECKOUTS is new.
+  "Add a new evaluation for SPEC-NAME only if one of the INSTANCES is new.
 Otherwise, return #f."
   (define now
     (or timestamp (time-second (current-time time-utc))))
@@ -539,10 +495,12 @@ VALUES (" spec-name "," (evaluation-status started) ","
 now "," checkouttime "," evaltime ")
 RETURNING id;"))
               ((id) (string->number id))))
-           (new-checkouts (filter-map
-                           (cut db-add-checkout spec-name eval-id <>)
-                           checkouts)))
-      (if (null? new-checkouts)
+           (new-instances (filter-map
+                           (lambda (instance)
+                             (db-add-checkout spec-name eval-id instance
+                                              #:timestamp timestamp))
+                           instances)))
+      (if (null? new-instances)
           (begin (exec-query db "ROLLBACK;")
                  #f)
           (begin (db-add-event 'evaluation
@@ -715,7 +673,7 @@ where id = " build-id ") d;
       (not (null? new-outputs))))
 
   (define (build-priority priority)
-    (let ((spec-priority (assq-ref specification #:priority)))
+    (let ((spec-priority (specification-priority specification)))
       (+ (* spec-priority 10) priority)))
 
   (define (register job)
@@ -750,7 +708,7 @@ where id = " build-id ") d;
                           (#:starttime . 0)
                           (#:stoptime . 0))))
              (if period
-                 (let* ((spec (assq-ref specification #:name))
+                 (let* ((spec (specification-name specification))
                         (time
                          (db-get-time-since-previous-build job-name spec))
                         (add-build? (cond
@@ -838,7 +796,8 @@ UPDATE Builds SET stoptime =" now
               (let* ((build (db-get-build drv))
                      (spec (assq-ref build #:specification))
                      (specification (db-get-specification spec))
-                     (notifications (assq-ref specification #:notifications)))
+                     (notifications
+                      (specification-notifications specification)))
                 (send-notifications notifications #:build build)
                 (db-add-event 'build
                               now
@@ -1244,16 +1203,16 @@ SELECT derivation FROM Builds WHERE Builds.status < 
0;"))))
 (define (db-get-checkouts eval-id)
   (with-db-worker-thread db
     (let loop ((rows (exec-query/bind
-                      db "SELECT revision, input, directory FROM Checkouts
-WHERE evaluation =" eval-id ";"))
+                      db "SELECT revision, channel, directory FROM Checkouts
+WHERE evaluation =" eval-id " ORDER BY channel ASC;"))
                (checkouts '()))
       (match rows
         (() (reverse checkouts))
-        (((revision input directory)
+        (((revision channel directory)
           . rest)
          (loop rest
                (cons `((#:commit . ,revision)
-                       (#:input . ,input)
+                       (#:channel . ,(string->symbol channel))
                        (#:directory . ,directory))
                      checkouts)))))))
 
diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm
index 4b5cbf9..0182d2c 100644
--- a/src/cuirass/http.scm
+++ b/src/cuirass/http.scm
@@ -28,8 +28,10 @@
   #:use-module (cuirass metrics)
   #:use-module (cuirass utils)
   #:use-module (cuirass logging)
+  #:use-module (cuirass notification)
   #:use-module (cuirass remote)
   #:use-module (cuirass rss)
+  #:use-module (cuirass specification)
   #:use-module (cuirass zabbix)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-11)
@@ -47,6 +49,7 @@
   #:use-module ((rnrs bytevectors) #:select (utf8->string))
   #:use-module (sxml simple)
   #:use-module (cuirass templates)
+  #:use-module (guix channels)
   #:use-module (guix progress)
   #:use-module (guix utils)
   #:use-module ((guix store) #:select (%store-prefix))
@@ -129,31 +132,42 @@
 
 (define (specification->json-object spec)
   "Turn SPEC into a representation suitable for 'json->scm'."
-  (define (atom? x)
-    (not (pair? x)))
-
-  (define (atom-list? obj)
-    (and (list? obj)
-         (every atom? obj)))
-
-  `((#:name . ,(assq-ref spec #:name))
-    (#:load-path-inputs . ,(list->vector
-                            (assq-ref spec #:load-path-inputs)))
-    (#:package-path-inputs . ,(list->vector
-                               (assq-ref spec #:package-path-inputs)))
-    (#:proc-input . ,(assq-ref spec #:proc-input))
-    (#:proc-file . ,(assq-ref spec #:proc-file))
-    (#:proc . ,(assq-ref spec #:proc))
-    (#:proc-args . ,(map (match-lambda
-                           ((key . arg)
-                            (cons key (if (atom-list? arg)
-                                          (list->vector arg)
-                                          arg))))
-                         (assq-ref spec #:proc-args)))
-    (#:inputs . ,(list->vector
-                  (assq-ref spec #:inputs)))
+  (define (channel->json-object channel)
+    `((#:name . ,(channel-name channel))
+      (#:url . ,(channel-url channel))
+      (#:branch . ,(channel-branch channel))
+      (#:commit . ,(channel-commit channel))))
+
+  (define (build-output->json-object build-output)
+    `((#:job . ,(build-output-job build-output))
+      (#:type . ,(build-output-type build-output))
+      (#:output . ,(build-output-output build-output))
+      (#:path . ,(build-output-path build-output))))
+
+  (define (notification->json-object notif)
+    (cond
+     ((email? notif)
+      `((#:type . email)
+        (#:from . ,(email-from notif))
+        (#:to . ,(email-to notif))
+        (#:server . ,(email-server notif))))
+     ((mastodon? notif)
+      `((#:type . mastodon)))))
+
+  `((#:name . ,(specification-name spec))
+    (#:build . ,(specification-build spec))
+    (#:channels . ,(list->vector
+                    (map channel->json-object
+                         (specification-channels spec))))
     (#:build-outputs . ,(list->vector
-                         (assq-ref spec #:build-outputs)))))
+                         (map build-output->json-object
+                              (specification-build-outputs spec))))
+    (#:notifications . ,(list->vector
+                         (map notification->json-object
+                              (specification-notifications spec))))
+    (#:priority . ,(specification-priority spec))
+    (#:systems . ,(list->vector
+                   (specification-systems spec)))))
 
 (define (handle-build-request build-id)
   "Retrieve build identified by BUILD-ID over the database and convert it to
@@ -211,8 +225,9 @@ Hydra format."
   (define builds-id-max (db-get-builds-max id status))
   (define builds-id-min (db-get-builds-min id status))
   (define specification (db-get-evaluation-specification id))
+  (define channels      (specification-channels
+                         (db-get-specification specification)))
   (define checkouts     (db-get-checkouts id))
-  (define inputs        (db-get-inputs specification))
 
   (define builds
     (vector->list
@@ -229,8 +244,8 @@ Hydra format."
   (html-page
    "Evaluation"
    (evaluation-build-table evaluation
+                           #:channels channels
                            #:checkouts checkouts
-                           #:inputs inputs
                            #:status status
                            #:builds builds
                            #:builds-id-min builds-id-min
diff --git a/src/cuirass/metrics.scm b/src/cuirass/metrics.scm
index f993cf2..efd6d16 100644
--- a/src/cuirass/metrics.scm
+++ b/src/cuirass/metrics.scm
@@ -19,6 +19,7 @@
 (define-module (cuirass metrics)
   #:use-module (cuirass database)
   #:use-module (cuirass logging)
+  #:use-module (cuirass specification)
   #:use-module (guix records)
   #:use-module (squee)
   #:use-module (srfi srfi-1)
@@ -375,7 +376,7 @@ UPDATE SET value = " value ", timestamp = " now ";"))
   ;; Limit to the evaluations that were added during the past three days.
   (with-db-worker-thread db
     (let ((specifications
-           (map (cut assq-ref <> #:name) (db-get-specifications)))
+           (map specification-name (db-get-specifications)))
           (evaluations (db-latest-evaluations)))
       (exec-query db "BEGIN TRANSACTION;")
 
diff --git a/src/cuirass/notification.scm b/src/cuirass/notification.scm
index 3de7f5b..5688c77 100644
--- a/src/cuirass/notification.scm
+++ b/src/cuirass/notification.scm
@@ -22,12 +22,68 @@
   #:use-module (cuirass mastodon)
   #:use-module (cuirass parameters)
   #:use-module (cuirass utils)
-  #:export (notification-type
-            notification-event
+  #:use-module (guix records)
+  #:use-module (ice-9 match)
+  #:export (email
+            email?
+            email-from
+            email-to
+            email-server
+
+            mastodon
+            mastodon?
+
+            notification->sexp
+            sexp->notification
 
             with-notification
             send-notifications))
 
+
+;;;
+;;; Notification types.
+;;;
+
+(define-record-type* <email>
+  email make-email
+  email?
+  (from     email-from) ;string
+  (to       email-to) ;string
+  (server   email-server)) ;string
+
+(define-record-type* <mastodon>
+  mastodon make-mastodon
+  mastodon?)
+
+(define (notification->sexp notif)
+  "Return an sexp describing NOTIF."
+  (cond
+   ((email? notif)
+    `(email
+      (from ,(email-from notif))
+      (to ,(email-to notif))
+      (server ,(email-server notif))))
+   ((mastodon? notif)
+    '(mastodon))))
+
+(define (sexp->notification sexp)
+  "Return the notification corresponding to SEXP."
+  (match sexp
+    (('email ('from from)
+             ('to to)
+             ('server server))
+     (email
+      (from from)
+      (to to)
+      (server server)))
+    (('mastodon)
+     (mastodon))))
+
+
+;;;
+;;; Send notifications.
+;;;
+
 ;; XXX: Some redefinitions to avoid a circular dependency with the (cuirass
 ;; database) module.
 (define weather-success 0)
@@ -54,15 +110,6 @@ interfering with fibers."
    (lambda args
      exp ...)))
 
-(define-enumeration notification-type
-  (email            0)
-  (mastodon         1))
-
-(define-enumeration notification-event
-  (always            0)
-  (broken-builds     1)
-  (fixed-builds      2))
-
 (define (build-weather-text build)
   "Return the build weather string."
   (let ((weather (assq-ref build #:weather)))
@@ -78,19 +125,17 @@ interfering with fibers."
         (url (or (%cuirass-url) "")))
     (string-append url "/build/" (number->string id) "/details")))
 
-(define (notification-subject notification)
+(define (notification-subject build)
   "Return the subject for the given NOTIFICATION."
-  (let* ((build (assq-ref notification #:build))
-         (job-name (assq-ref build #:job-name))
+  (let* ((job-name (assq-ref build #:job-name))
          (specification (assq-ref build #:specification))
          (weather-text (build-weather-text build)))
     (format #f "Build ~a on ~a is ~a."
             job-name specification weather-text)))
 
-(define (notification-text notification)
+(define (notification-text build)
   "Return the text for the given NOTIFICATION."
-  (let* ((build (assq-ref notification #:build))
-         (url (build-details-url build))
+  (let* ((url (build-details-url build))
          (job-name (assq-ref build #:job-name))
          (specification (assq-ref build #:specification))
          (weather-text (build-weather-text build)))
@@ -98,13 +143,13 @@ interfering with fibers."
 the detailed information about this build here: ~a."
             job-name specification weather-text url)))
 
-(define (notification-email notification)
+(define (send-email* notif build)
   "Send an email for the given NOTIFICATION."
-  (let* ((from (assq-ref notification #:from))
-         (to (assq-ref notification #:to))
-         (server (assq-ref notification #:server))
-         (subject (notification-subject notification))
-         (text (notification-text notification)))
+  (let* ((from (email-from notif))
+         (to (email-to notif))
+         (server (email-server notif))
+         (subject (notification-subject build))
+         (text (notification-text build)))
     (catch #t
       (lambda ()
         (send-email server
@@ -116,9 +161,9 @@ the detailed information about this build here: ~a."
         (log-message "Failed to send the email notification: ~a."
                      args)))))
 
-(define (notification-mastodon notification)
+(define (send-mastodon build)
   "Send a new status for the given NOTIFICATION."
-  (let ((text (notification-text notification)))
+  (let ((text (notification-text build)))
     (catch #t
       (lambda ()
         (send-status text))
@@ -131,22 +176,9 @@ the detailed information about this build here: ~a."
   (with-notification-worker-thread
    (for-each
     (lambda (notification)
-      (let* ((event (assq-ref notification #:event))
-             (type (assq-ref notification #:type))
-             (weather (assq-ref build #:weather))
-             (success? (eq? weather weather-success))
-             (failure? (eq? weather weather-failure)))
-        (when (or
-               (and (eq? event (notification-event always))
-                    (or success? failure?))
-               (and (eq? event (notification-event broken-builds))
-                    failure?)
-               (and (eq? event (notification-event fixed-builds))
-                    success?))
-          (let ((notification* (acons #:build build notification)))
-            (cond
-             ((eq? type (notification-type email))
-              (notification-email notification*))
-             ((eq? type (notification-type mastodon))
-              (notification-mastodon notification*)))))))
+      (cond
+       ((email? notification)
+        (send-email* notification build))
+       ((mastodon? notification)
+        (send-mastodon build))))
     notifications)))
diff --git a/src/cuirass/specification.scm b/src/cuirass/specification.scm
new file mode 100644
index 0000000..79565bf
--- /dev/null
+++ b/src/cuirass/specification.scm
@@ -0,0 +1,153 @@
+;;; specification.scm -- Specification definition.
+;;; Copyright © 2021 Mathieu Othacehe <othacehe@gnu.org>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (cuirass specification)
+  #:use-module (guix channels)
+  #:use-module (guix records)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (ice-9 match)
+  #:export (build-output
+            build-output?
+            build-output-job
+            build-output-type
+            build-output-output
+            build-output-path
+
+            build-output->sexp
+            sexp->build-output
+
+            channel->sexp
+
+            specification
+            specification?
+            specification-name
+            specification-build
+            specification-channels
+            specification-priority
+            specification-build-outputs
+            specification-notifications
+            specification-systems
+
+            specification->sexp
+            sexp->specification
+            read-specifications))
+
+
+;;;
+;;; Build output record.
+;;;
+
+(define-record-type* <build-output>
+  build-output make-build-output
+  build-output?
+  (job                build-output-job) ;string
+  (type               build-output-type) ;string
+  (output             build-output-output) ;string
+  (path               build-output-path)) ;string
+
+(define (build-output->sexp build-output)
+  "Return an sexp describing BUILD-OUTPUT."
+  `(build-output
+    (job ,(build-output-job build-output))
+    (type ,(build-output-type build-output))
+    (output ,(build-output-output build-output))
+    (path ,(build-output-path build-output))))
+
+(define (sexp->build-output sexp)
+  "Return the build-output corresponding to SEXP."
+  (match sexp
+    (('build-output ('job job)
+                    ('type type)
+                    ('output output)
+                    ('path path))
+     (build-output
+      (job job)
+      (type type)
+      (output output)
+      (path path)))))
+
+
+;;;
+;;; Channels.
+;;;
+
+(define (channel->sexp channel)
+  "Return an sexp describing CHANNEL."
+  `(repository
+    (version 0)
+    (url ,(channel-url channel))
+    (branch ,(channel-branch channel))
+    (commit ,(channel-commit channel))
+    (name ,(channel-name channel))))
+
+
+;;;
+;;; Specification record.
+;;;
+
+(define-record-type* <specification>
+  specification make-specification
+  specification?
+  (name               specification-name) ;symbol
+  (build              specification-build ;symbol
+                      (default 'all))
+  (channels           specification-channels ;list of <channel>
+                      (default (list %default-guix-channel)))
+  (build-outputs      specification-build-outputs ;list of <build-output>
+                      (default '()))
+  (notifications      specification-notifications
+                      (default '()))
+  (priority           specification-priority ;integer
+                      (default 9))
+  (systems            specification-systems ;list of strings
+                      (default (list (%current-system)))))
+
+(define (specification->sexp spec)
+  "Return an sexp describing SPEC."
+  `(specification (name ,(specification-name spec))
+                  (build ,(specification-build spec))
+                  (channels ,(specification-channels spec))
+                  (build-outputs ,(specification-build-outputs spec))
+                  (notifications ,(specification-notifications spec))
+                  (priority ,(specification-priority spec))
+                  (systems ,(specification-systems spec))))
+
+(define (sexp->specification sexp)
+  "Return the specification corresponding to SEXP."
+  (match sexp
+    (('specification ('name name)
+                     ('build build)
+                     ('channels channels)
+                     ('build-outputs build-outputs)
+                     ('notifications notifications)
+                     ('priority priority)
+                     ('systems systems))
+     (specification (name name)
+                    (build build)
+                    (channels channels)
+                    (build-outputs build-outputs)
+                    (notifications notifications)
+                    (priority priority)
+                    (systems systems)))))
+
+(define (read-specifications file)
+  (let ((modules (make-user-module '((guix channels)
+                                     (cuirass notification)
+                                     (cuirass specification)))))
+    (load* file modules)))
diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm
index 1e3abbd..a6cef8f 100644
--- a/src/cuirass/templates.scm
+++ b/src/cuirass/templates.scm
@@ -28,6 +28,7 @@
   #:use-module (srfi srfi-26)
   #:use-module (json)
   #:use-module (web uri)
+  #:use-module (guix channels)
   #:use-module (guix derivations)
   #:use-module (guix progress)
   #:use-module (guix store)
@@ -37,6 +38,7 @@
                                              build-weather
                                              evaluation-status))
   #:use-module (cuirass remote)
+  #:use-module (cuirass specification)
   #:export (html-page
             specifications-table
             evaluation-info-table
@@ -162,27 +164,45 @@ system whose names start with " (code "guile-") ":" (br)
 
 (define (status-class status)
   (cond
-    ((= (build-status submitted)         status) "oi oi-clock         
text-warning")
-    ((= (build-status scheduled)         status) "oi oi-clock         
text-warning")
-    ((= (build-status started)           status) "oi oi-reload        
text-warning")
-    ((= (build-status succeeded)         status) "oi oi-check         
text-success")
-    ((= (build-status failed)            status) "oi oi-x             
text-danger")
-    ((= (build-status failed-dependency) status) "oi oi-warning       
text-danger")
-    ((= (build-status failed-other)      status) "oi oi-warning       
text-danger")
-    ((= (build-status canceled)          status) "oi oi-question-mark 
text-warning")
-    (else                                        "oi oi-warning       
text-danger")))
+   ((= (build-status submitted) status)
+    "oi oi-clock text-warning")
+   ((= (build-status scheduled) status)
+    "oi oi-clock text-warning")
+   ((= (build-status started) status)
+    "oi oi-reload text-warning")
+   ((= (build-status succeeded) status)
+    "oi oi-check text-success")
+   ((= (build-status failed) status)
+    "oi oi-x text-danger")
+   ((= (build-status failed-dependency) status)
+    "oi oi-warning text-danger")
+   ((= (build-status failed-other) status)
+    "oi oi-warning text-danger")
+   ((= (build-status canceled) status)
+    "oi oi-question-mark text-warning")
+   (else
+    "oi oi-warning text-danger")))
 
 (define (status-title status)
   (cond
-    ((= (build-status submitted)         status) "Submitted")
-    ((= (build-status scheduled)         status) "Scheduled")
-    ((= (build-status started)           status) "Started")
-    ((= (build-status succeeded)         status) "Succeeded")
-    ((= (build-status failed)            status) "Failed")
-    ((= (build-status failed-dependency) status) "Failed (dependency)")
-    ((= (build-status failed-other)      status) "Failed (other)")
-    ((= (build-status canceled)          status) "Canceled")
-    (else                                        "Invalid status")))
+   ((= (build-status submitted) status)
+    "Submitted")
+   ((= (build-status scheduled) status)
+    "Scheduled")
+   ((= (build-status started) status)
+    "Started")
+   ((= (build-status succeeded) status)
+    "Succeeded")
+   ((= (build-status failed) status)
+    "Failed")
+   ((= (build-status failed-dependency) status)
+    "Failed (dependency)")
+   ((= (build-status failed-other) status)
+    "Failed (other)")
+   ((= (build-status canceled) status)
+    "Canceled")
+   (else
+    "Invalid status")))
 
 (define* (specifications-table specs #:optional admin?)
   "Return HTML for the SPECS table."
@@ -199,37 +219,42 @@ system whose names start with " (code "guile-") ":" (br)
      ,@(if (null? specs)
            `((th (@ (scope "col")) "No elements here."))
            `((thead (tr (th (@ (scope "col")) Name)
-                        (th (@ (scope "col")) Inputs)
+                        (th (@ (scope "col")) Channels)
                         ,@(if admin?
                               '((th (@ (scope "col")) Action))
                               '())))
              (tbody
               ,@(map
                  (lambda (spec)
-                   `(tr (td (a (@ (href "/jobset/" ,(assq-ref spec #:name)))
-                               ,(assq-ref spec #:name)))
+                   `(tr (td (a (@ (href "/jobset/"
+                                        ,(specification-name spec)))
+                               ,(specification-name spec)))
                         (td ,(string-join
-                              (map (lambda (input)
+                              (map (lambda (channel)
                                      (format #f "~a (on ~a)"
-                                             (assq-ref input #:name)
-                                             (assq-ref input #:branch)))
-                                   (assq-ref spec #:inputs)) ", "))
+                                             (channel-name channel)
+                                             (channel-branch channel)))
+                                   (specification-channels spec)) ", "))
                         ,@(if admin?
-                              `((form (@ (class "form")
-                                         (action ,(string-append 
"/admin/specifications/delete/"
-                                                                 (assq-ref 
spec #:name)))
-                                         (method "POST")
-                                         (onsubmit
-                                          ,(string-append "return 
confirm('Please confirm deletion of specification "
-                                                          (assq-ref spec 
#:name)
-                                                          ".');")))
+                              `((form
+                                 (@ (class "form")
+                                    (action
+                                     ,(string-append
+                                       "/admin/specifications/delete/"
+                                       (specification-name spec)))
+                                    (method "POST")
+                                    (onsubmit
+                                     ,(string-append
+                                       "return confirm('Please confirm 
deletion of specification "
+                                       (specification-name spec) ".');")))
                                       `((div
                                          (@ (class "input-group"))
-                                         (span (@ (class "input-group-append"))
-                                               (button
-                                                (@ (type "submit")
-                                                   (class "btn"))
-                                                "Remove"))))))
+                                         (span
+                                          (@ (class "input-group-append"))
+                                          (button
+                                           (@ (type "submit")
+                                              (class "btn"))
+                                           "Remove"))))))
                               '())))
                  specs))))
      ,@(if admin?
@@ -437,7 +462,7 @@ system whose names start with " (code "guile-") ":" (br)
   (let ((changes
          (string-join
           (map (lambda (checkout)
-                 (let ((input (assq-ref checkout #:input))
+                 (let ((input (assq-ref checkout #:channel))
                        (commit (assq-ref checkout #:commit)))
                    (format #f "~a → ~a" input (substring commit 0 7))))
                checkouts)
@@ -496,7 +521,7 @@ system whose names start with " (code "guile-") ":" (br)
            `((thead
               (tr
                (th (@ (scope "col")) "#")
-               (th (@ (scope "col")) "Input changes")
+               (th (@ (scope "col")) "Channel changes")
                (th (@ (scope "col")) Success)
                (th (@ (scope "col")) Action)))
              (tbody
@@ -730,8 +755,8 @@ the nearest exact even integer."
 
 (define* (evaluation-build-table evaluation
                                  #:key
+                                 channels
                                  (checkouts '())
-                                 (inputs '())
                                  status builds
                                  builds-id-min builds-id-max)
   "Return HTML for an evaluation page, containing a table of builds for that
@@ -759,20 +784,20 @@ evaluation."
                              (seconds->string duration))))))
     (table (@ (class "table table-sm table-hover"))
            (thead
-            (tr (th (@ (class "border-0") (scope "col")) "Input")
+            (tr (th (@ (class "border-0") (scope "col")) "Channel")
                 (th (@ (class "border-0") (scope "col")) "Commit")))
            (tbody
             ,@(map (lambda (checkout)
-                     (let* ((name  (assq-ref checkout #:input))
-                            (input (find (lambda (input)
-                                           (string=? (assq-ref input #:name)
-                                                     name))
-                                         inputs))
-                            (url   (assq-ref input #:url))
+                     (let* ((name  (assq-ref checkout #:channel))
+                            (channel (find (lambda (channel)
+                                           (eq? (channel-name channel)
+                                                name))
+                                         channels))
+                            (url   (channel-url channel))
                             (commit (assq-ref checkout #:commit)))
                        ;; Some checkout entries may refer to removed
                        ;; inputs.
-                       (if input
+                       (if channel
                            `(tr (td ,url)
                                 (td (code ,(commit-hyperlink url commit))))
                            '())))
diff --git a/src/schema.sql b/src/schema.sql
index d4929e2..b0838d6 100644
--- a/src/schema.sql
+++ b/src/schema.sql
@@ -6,29 +6,12 @@ CREATE TABLE SchemaVersion (
 
 CREATE TABLE Specifications (
   name          TEXT NOT NULL PRIMARY KEY,
-  load_path_inputs TEXT NOT NULL, -- list of input names whose load path will 
be in Guile's %load-path
-  package_path_inputs TEXT NOT NULL, -- list of input names whose load paths 
will be in GUIX_PACKAGE_PATH
-  proc_input    TEXT NOT NULL, -- name of the input containing the proc that 
does the evaluation
-  proc_file     TEXT NOT NULL, -- file containing the procedure that does the 
evaluation, relative to proc_input
-  proc          TEXT NOT NULL, -- defined in proc_file
-  proc_args     TEXT NOT NULL, -- passed to proc
-  build_outputs TEXT NOT NULL, --specify what build outputs should be made 
available for download
+  build         TEXT NOT NULL,
+  channels      TEXT NOT NULL,
+  build_outputs TEXT NOT NULL,
+  notifications TEXT NOT NULL,
   priority      INTEGER NOT NULL DEFAULT 0,
-  notifications TEXT
-);
-
-CREATE TABLE Inputs (
-  specification TEXT NOT NULL,
-  name          TEXT NOT NULL,
-  url           TEXT NOT NULL,
-  load_path     TEXT NOT NULL,
-  -- The following columns are optional.
-  branch        TEXT,
-  tag           TEXT,
-  revision      TEXT,
-  no_compile_p  INTEGER,
-  PRIMARY KEY (specification, name),
-  FOREIGN KEY (specification) REFERENCES Specifications(name)
+  systems       TEXT NOT NULL
 );
 
 CREATE TABLE Evaluations (
@@ -45,13 +28,12 @@ CREATE TABLE Checkouts (
   specification TEXT NOT NULL,
   revision      TEXT NOT NULL,
   evaluation    INTEGER NOT NULL,
-  input         TEXT NOT NULL,
+  channel       TEXT NOT NULL,
   directory     TEXT NOT NULL,
   timestamp     INTEGER NOT NULL,
   PRIMARY KEY (specification, revision),
   FOREIGN KEY (evaluation) REFERENCES Evaluations(id),
-  FOREIGN KEY (specification) REFERENCES Specifications(name),
-  FOREIGN KEY (specification, input) REFERENCES Inputs(specification, name)
+  FOREIGN KEY (specification) REFERENCES Specifications(name)
 );
 
 CREATE TABLE Builds (
@@ -135,6 +117,4 @@ CREATE INDEX Evaluations_specification_index ON Evaluations 
(specification, id D
 
 CREATE INDEX Outputs_derivation_index ON Outputs (derivation);
 
-CREATE INDEX Inputs_index ON Inputs(specification, name, branch);
-
 COMMIT;
diff --git a/tests/database.scm b/tests/database.scm
index 03fdd65..a911bf5 100644
--- a/tests/database.scm
+++ b/tests/database.scm
@@ -24,7 +24,9 @@
              (cuirass notification)
              (cuirass parameters)
              (cuirass remote)
+             (cuirass specification)
              (cuirass utils)
+             (guix channels)
              ((guix utils) #:select (call-with-temporary-output-file))
              (rnrs io ports)
              (squee)
@@ -42,43 +44,40 @@
 (false-if-exception (delete-file tmp-mail))
 
 (define example-spec
-  `((#:name . "guix")
-    (#:load-path-inputs . ("savannah"))
-    (#:package-path-inputs . ())
-    (#:proc-input . "savannah")
-    (#:proc-file . "/tmp/gnu-system.scm")
-    (#:proc . hydra-jobs)
-    (#:proc-args (subset . "hello"))
-    (#:inputs . (((#:name . "maintenance")
-                  (#:url . "git://git.savannah.gnu.org/guix/maintenance.git")
-                  (#:load-path . ".")
-                  (#:branch . "master")
-                  (#:tag . #f)
-                  (#:commit . #f)
-                  (#:no-compile? . #f))
-                 ((#:name . "savannah")
-                  (#:url . "git://git.savannah.gnu.org/guix.git")
-                  (#:load-path . ".")
-                  (#:branch . "master")
-                  (#:tag . #f)
-                  (#:commit . #f)
-                  (#:no-compile? . #f))))
-    (#:build-outputs . ())
-    (#:notifications . (((#:name . "name")
-                         (#:type . ,(notification-type email))
-                         (#:from . "from")
-                         (#:to . "to")
-                         (#:server . ,(mailer))
-                         (#:event . 0))))
-    (#:priority . 9)))
-
-(define (make-dummy-checkouts fakesha1 fakesha2)
-  `(((#:commit . ,fakesha1)
-     (#:input . "savannah")
-     (#:directory . "foo"))
-    ((#:commit . ,fakesha2)
-     (#:input . "maintenance")
-     (#:directory . "bar"))))
+  (specification
+   (name "guix")
+   (build 'hello)
+   (channels
+    (list (channel
+           (name 'guix)
+           (url "git://git.savannah.gnu.org/guix.git")
+           (branch "master"))
+          (channel
+           (name 'my-channel)
+           (url "git://my-git-channel.git")
+           (branch "master"))))
+   (build-outputs
+    (list (build-output
+           (job "job")
+           (type "type")
+           (output "out")
+           (path ""))))
+   (notifications
+    (list (email
+           (from "from")
+           (to "to")
+           (server (mailer)))))))
+
+(define (make-dummy-instances fakesha1 fakesha2)
+  (list
+   (checkout->channel-instance "foo"
+                               #:name 'guix
+                               #:url "git://git.savannah.gnu.org/guix.git"
+                               #:commit fakesha1)
+   (checkout->channel-instance "bar"
+                               #:name 'my-channel
+                               #:url "git://my-git-channel.git"
+                               #:commit fakesha2)))
 
 (define* (make-dummy-build drv
                            #:optional (eval-id 2)
@@ -130,17 +129,21 @@ INSERT INTO Evaluations (specification, status,
 timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 0);")
       (exec-query (%db) "SELECT * FROM Evaluations;")))
 
-  (test-equal "db-get-specification"
-    example-spec
-    (db-get-specification "guix"))
+  (test-assert "db-get-specification"
+    (let* ((spec (db-get-specification "guix"))
+           (channels (specification-channels spec))
+           (build-outputs (specification-build-outputs spec)))
+      (and (string=? (specification-name spec) "guix")
+           (equal? (map channel-name channels) '(guix my-channel))
+           (equal? (map build-output-job build-outputs) '("job")))))
 
   (test-equal "db-add-evaluation"
     '(2 3)
     (list
      (db-add-evaluation "guix"
-                        (make-dummy-checkouts "fakesha1" "fakesha2"))
+                        (make-dummy-instances "fakesha1" "fakesha2"))
      (db-add-evaluation "guix"
-                        (make-dummy-checkouts "fakesha3" "fakesha4"))))
+                        (make-dummy-instances "fakesha3" "fakesha4"))))
 
   (test-assert "db-set-evaluation-status"
     (db-set-evaluation-status 2 (evaluation-status started)))
@@ -181,14 +184,14 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
 
   (test-assert "db-register-builds"
     (let ((drv "/test.drv"))
-      (db-register-builds `(((#:job-name . "test")
-                             (#:derivation . ,drv)
-                             (#:system . "x86_64-linux")
-                             (#:nix-name . "test")
-                             (#:log . "log")
-                             (#:outputs .
-                              (("foo" . ,(format #f "~a.output" drv))
-                               ("foo2" . ,(format #f "~a.output.2" drv))))))
+      (db-register-build `((#:job-name . "test")
+                           (#:derivation . ,drv)
+                           (#:system . "x86_64-linux")
+                           (#:nix-name . "test")
+                           (#:log . "log")
+                           (#:outputs .
+                            (("foo" . ,(format #f "~a.output" drv))
+                             ("foo2" . ,(format #f "~a.output.2" drv)))))
                           2 (db-get-specification "guix"))))
 
   (test-assert "db-update-build-status!"
@@ -251,9 +254,11 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 
0, 0);")
     '("/foo.drv")
     (db-get-pending-derivations))
 
-  (test-assert "db-get-checkouts"
-    (equal? (db-get-checkouts 2)
-            (make-dummy-checkouts "fakesha1" "fakesha2")))
+  (test-equal "db-get-checkouts"
+    '("fakesha1" "fakesha2")
+    (begin
+      (make-dummy-instances "fakesha1" "fakesha2")
+      (map (cut assq-ref <> #:commit) (db-get-checkouts 2))))
 
   (test-equal "db-get-evaluation"
     "guix"
@@ -476,7 +481,7 @@ timestamp, checkouttime, evaltime) VALUES ('guix', 0, 0, 0, 
0);")
     (list #f 1)
     (begin
       (db-add-evaluation "guix"
-                         (make-dummy-checkouts "fakesha5" "fakesha6"))
+                         (make-dummy-instances "fakesha5" "fakesha6"))
       (db-add-build (make-dummy-build "/old-build.drv" 3
                                       #:job-name "job-1"
                                       #:outputs `(("out" . "/old"))))
diff --git a/tests/http.scm b/tests/http.scm
index b814c4e..776b8db 100644
--- a/tests/http.scm
+++ b/tests/http.scm
@@ -1,7 +1,7 @@
 ;;; http.scm -- tests for (cuirass http) module
 ;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
 ;;; Copyright © 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
-;;; Copyright © 2017, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
+;;; Copyright © 2017, 2020, 2021 Mathieu Othacehe <m.othacehe@gmail.com>
 ;;; Copyright © 2018 Clément Lassieur <clement@lassieur.org>
 ;;;
 ;;; This file is part of Cuirass.
@@ -21,7 +21,9 @@
 
 (use-modules (cuirass http)
              (cuirass database)
+             (cuirass specification)
              (cuirass utils)
+             (guix channels)
              (json)
              (fibers)
              (squee)
@@ -81,7 +83,7 @@
      (#:checkouttime . 0)
      (#:evaltime . 0)
      (#:checkouts . #(((#:commit . "fakesha2")
-                       (#:input . "savannah")
+                       (#:channel . "guix")
                        (#:directory . "dir3")))))))
 
 (test-group-with-cleanup "http"
@@ -151,44 +153,40 @@
               (#:timestamp . 1501347493)
               (#:starttime . 0)
               (#:stoptime . 0)))
-           (specification
-            '((#:name . "guix")
-              (#:load-path-inputs . ("savannah"))
-              (#:package-path-inputs . ())
-              (#:proc-input . "savannah")
-              (#:proc-file . "/tmp/gnu-system.scm")
-              (#:proc . hydra-jobs)
-              (#:proc-args (subset . "hello"))
-              (#:inputs . (((#:name . "savannah")
-                            (#:url . "git://git.savannah.gnu.org/guix.git")
-                            (#:load-path . ".")
-                            (#:branch . "master")
-                            (#:tag . #f)
-                            (#:commit . #f)
-                            (#:no-compile? . #f))
-                           ((#:name . "packages")
-                            (#:url . "git://git.savannah.gnu.org/guix.git")
-                            (#:load-path . ".")
-                            (#:branch . "master")
-                            (#:tag . #f)
-                            (#:commit . #f)
-                            (#:no-compile? . #f))))
-              (#:build-outputs . ())))
+           (spec
+            (specification
+             (name "guix")
+             (build 'hello)
+             (channels
+              (list (channel
+                     (name 'guix)
+                     (url "https://gitlab.com/mothacehe/guix.git";)
+                     (branch "master"))
+                    (channel
+                     (name 'packages)
+                     (url "https://gitlab.com/mothacehe/guix.git";)
+                     (branch "master"))))))
            (checkouts1
-            '(((#:commit . "fakesha1")
-               (#:input . "savannah")
-               (#:directory . "dir1"))
-              ((#:commit . "fakesha3")
-               (#:input . "packages")
-               (#:directory . "dir2"))))
+            (list
+             (checkout->channel-instance "dir1"
+                                         #:name 'guix
+                                         #:url "url1"
+                                         #:commit "fakesha1")
+             (checkout->channel-instance "dir2"
+                                         #:name 'packages
+                                         #:url "url2"
+                                         #:commit "fakesha3")))
            (checkouts2
-            '(((#:commit . "fakesha2")
-               (#:input . "savannah")
-               (#:directory . "dir3"))
-              ((#:commit . "fakesha3")
-               (#:input . "packages")
-               (#:directory . "dir4")))))
-      (db-add-specification specification)
+            (list
+             (checkout->channel-instance "dir3"
+                                         #:name 'guix
+                                         #:url "dir3"
+                                         #:commit "fakesha2")
+             (checkout->channel-instance "dir4"
+                                         #:name 'packages
+                                         #:url "dir4"
+                                         #:commit "fakesha3"))))
+      (db-add-specification spec)
       (db-add-evaluation "guix" checkouts1
                          #:timestamp 1501347493)
       (db-add-evaluation "guix" checkouts2
@@ -202,8 +200,7 @@
                 (http-get-body (test-cuirass-uri "/specifications")))
              json->scm)
       (#(spec)
-       (and (string=? (assoc-ref spec "name") "guix")
-            (vector? (assoc-ref spec "package-path-inputs"))))))
+       (string=? (assoc-ref spec "name") "guix"))))
 
   (test-assert "/build/1"
     (lset= equal?
diff --git a/tests/metrics.scm b/tests/metrics.scm
index 7a1b0b4..e7ab5ff 100644
--- a/tests/metrics.scm
+++ b/tests/metrics.scm
@@ -47,10 +47,9 @@
   (test-assert "exec-query"
     (begin
       (exec-query (%db) "\
-INSERT INTO Specifications (name, load_path_inputs, package_path_inputs,
-proc_input, proc_file, proc, proc_args, build_outputs, priority)
-VALUES ('guix', '()', '()', 'guix',' build-aux/cuirass/gnu-system.scm',
-'cuirass-jobs', '', '', 2);")
+INSERT INTO Specifications (name, build, channels, build_outputs,
+notifications, priority, systems)
+VALUES ('guix', 'hello', '()', '()', '()', 9, '()');")
       (exec-query (%db) "\
 INSERT INTO Evaluations (specification, status,
 timestamp, checkouttime, evaltime) VALUES ('guix', -1, 1600174547, 0, 0);")



reply via email to

[Prev in Thread] Current Thread [Next in Thread]