From f44618fc79a730944cd3165a390d896777a8dad0 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Wed, 10 Jun 2020 15:58:11 +0200 Subject: [PATCH] Add support for build products downloading. * src/sql/upgrade-7.sql: New file. * Makefile.am: Add it. * src/cuirass/base.scm (create-build-outputs): New procedure, (build-packages): call it, (process-spec): add the new spec argument and pass it to create-build-outputs. * src/cuirass/database.scm (db-add-build-product, db-get-build-product-path, db-get-build-products): New exported procedures. * src/cuirass/http.scm (respond-static-file): Move file sending to ... (respond-file): ... this new procedure, (url-handler): add a new "download/" route, serving the requested file with the new respond-file procedure. Also gather build products and pass them to "build-details" for "build//details" route. * src/cuirass/templates.scm (build-details): Honor the new "products" argument to display all the build products associated to the given build. * src/schema.sql (BuildProducts): New table, (Specifications)[build_outputs]: new field. * tests/database.scm: Add empty build-outputs spec. * tests/http.scm: Ditto. * examples/guix-jobs.scm: Ditto. * examples/hello-git.scm: Ditto. * examples/hello-singleton.scm: Ditto. * examples/hello-subset.scm: Ditto. * examples/random.scm: Ditto. * doc/cuirass.texi (overview): Document it. --- Makefile.am | 4 ++- doc/cuirass.texi | 14 +++++++-- examples/guix-jobs.scm | 4 ++- examples/hello-git.scm | 4 ++- examples/hello-singleton.scm | 4 ++- examples/hello-subset.scm | 4 ++- examples/random.scm | 4 ++- src/cuirass/base.scm | 44 ++++++++++++++++++++++++++-- src/cuirass/database.scm | 57 ++++++++++++++++++++++++++++++++---- src/cuirass/http.scm | 36 +++++++++++++++++------ src/cuirass/templates.scm | 37 +++++++++++++++++++++-- src/schema.sql | 13 +++++++- src/sql/upgrade-7.sql | 15 ++++++++++ tests/database.scm | 4 ++- tests/http.scm | 5 ++-- 15 files changed, 218 insertions(+), 31 deletions(-) create mode 100644 src/sql/upgrade-7.sql diff --git a/Makefile.am b/Makefile.am index 65c9a29..f4a3663 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,6 +5,7 @@ # Copyright © 2018 Ludovic Courtès # Copyright © 2018 Clément Lassieur # Copyright © 2018 Tatiana Sholokhova +# Copyright © 2020 Mathieu Othacehe # # This file is part of Cuirass. # @@ -71,7 +72,8 @@ dist_sql_DATA = \ src/sql/upgrade-3.sql \ src/sql/upgrade-4.sql \ src/sql/upgrade-5.sql \ - src/sql/upgrade-6.sql + src/sql/upgrade-6.sql \ + src/sql/upgrade-7.sql dist_css_DATA = \ src/static/css/cuirass.css \ diff --git a/doc/cuirass.texi b/doc/cuirass.texi index e652e8d..c6f64c9 100644 --- a/doc/cuirass.texi +++ b/doc/cuirass.texi @@ -11,7 +11,7 @@ This manual is for Cuirass version @value{VERSION}, a build automation server. Copyright @copyright{} 2016, 2017 Mathieu Lirzin@* -Copyright @copyright{} 2017 Mathieu Othacehe@* +Copyright @copyright{} 2017, 2020 Mathieu Othacehe@* Copyright @copyright{} 2018 Ludovic Courtès@* Copyright @copyright{} 2018 Clément Lassieur @@ -137,7 +137,12 @@ a specification might look like: (#:url . "git://my-custom-packages.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t))))) + (#:no-compile? . #t)))) + (#:build-outputs . + (((#:job . "hello*") + (#:type . "license") + (#:output . "out") + (#:path . "share/doc/hello-2.10/COPYING"))))) @end lisp In this specification the keys are Scheme keywords which have the nice @@ -150,6 +155,11 @@ containing the custom packages (see @code{GUIX_PACKAGE_PATH}). @code{#:load-path-inputs}, @code{#:package-path-inputs} and @code{#:proc-input} refer to these inputs by their name. +The @code{#:build-outputs} list specifies the files that will be made +available for download, through the Web interface. Here, the +@code{COPYING} file, in the @code{"out"} output, for all jobs whose name +matches @code{"hello*"} regex. + @quotation Note @c This refers to @c . diff --git a/examples/guix-jobs.scm b/examples/guix-jobs.scm index 963c7ff..2f1f1a2 100644 --- a/examples/guix-jobs.scm +++ b/examples/guix-jobs.scm @@ -1,6 +1,7 @@ ;;; guix-jobs.scm -- job specification test for Guix ;;; Copyright © 2016 Mathieu Lirzin ;;; Copyright © 2018 Clément Lassieur +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -34,7 +35,8 @@ (#:url . "https://git.savannah.gnu.org/git/guix/guix-cuirass.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t)))))) + (#:no-compile? . #t)))) + (#:build-outputs . ()))) (define guix-master (job-base #:branch "master")) diff --git a/examples/hello-git.scm b/examples/hello-git.scm index 6468452..c5e2ca2 100644 --- a/examples/hello-git.scm +++ b/examples/hello-git.scm @@ -2,6 +2,7 @@ ;;; Copyright © 2016 Mathieu Lirzin ;;; Copyright © 2016 Jan Nieuwenhuizen ;;; Copyright © 2018 Clément Lassieur +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -43,4 +44,5 @@ (#:url . ,(string-append "file://" top-srcdir)) (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t))))))) + (#:no-compile? . #t)))) + (#:build-outputs . ())))) diff --git a/examples/hello-singleton.scm b/examples/hello-singleton.scm index a39191f..2d2d746 100644 --- a/examples/hello-singleton.scm +++ b/examples/hello-singleton.scm @@ -1,6 +1,7 @@ ;;; hello-singleton.scm -- job specification test for hello in master ;;; Copyright © 2016 Mathieu Lirzin ;;; Copyright © 2018 Clément Lassieur +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -34,6 +35,7 @@ (#:url . "https://git.savannah.gnu.org/git/guix/guix-cuirass.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t)))))) + (#:no-compile? . #t)))) + (#:build-outputs . ()))) (list hello-master) diff --git a/examples/hello-subset.scm b/examples/hello-subset.scm index 8c0d990..e86668e 100644 --- a/examples/hello-subset.scm +++ b/examples/hello-subset.scm @@ -1,6 +1,7 @@ ;;; hello-subset.scm -- job specification test for hello subset ;;; Copyright © 2016 Mathieu Lirzin ;;; Copyright © 2018 Clément Lassieur +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -34,7 +35,8 @@ (#:url . "https://git.savannah.gnu.org/git/guix/guix-cuirass.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t)))))) + (#:no-compile? . #t)))) + (#:build-outputs . ()))) (define guix-master (job-base #:branch "master")) diff --git a/examples/random.scm b/examples/random.scm index 37b97a2..f15e158 100644 --- a/examples/random.scm +++ b/examples/random.scm @@ -1,6 +1,7 @@ ;;; random.scm -- Job specification that creates random build jobs ;;; Copyright © 2018 Ludovic Courtès ;;; Copyright © 2018 Clément Lassieur +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -31,4 +32,5 @@ (#:url . ,(string-append "file://" top-srcdir)) (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t))))))) + (#:no-compile? . #t)))) + (#:build-outputs . ())))) diff --git a/src/cuirass/base.scm b/src/cuirass/base.scm index 2b18dc6..61cbde7 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 ;;; Copyright © 2016, 2017 Mathieu Lirzin -;;; Copyright © 2017 Mathieu Othacehe +;;; Copyright © 2017, 2020 Mathieu Othacehe ;;; Copyright © 2017 Ricardo Wurmus ;;; Copyright © 2018 Clément Lassieur ;;; @@ -41,6 +41,7 @@ #:use-module (ice-9 popen) #:use-module (ice-9 rdelim) #:use-module (ice-9 receive) + #:use-module (ice-9 regex) #:use-module (ice-9 atomic) #:use-module (ice-9 ftw) #:use-module (ice-9 threads) @@ -638,7 +639,42 @@ started)." (spawn-builds store valid) (log-message "done with restarted builds")))) -(define (build-packages store jobs eval-id) +(define (create-build-outputs builds product-specs) + "Given BUILDS a list of built derivations, save the build products described +by PRODUCT-SPECS." + (define (find-build job-regex) + (find (lambda (build) + (let ((job-name (assq-ref build #:job-name))) + (string-match job-regex job-name))) + builds)) + + (define* (find-product build spec) + (let* ((outputs (assq-ref build #:outputs)) + (output (assq-ref spec #:output)) + (path (assq-ref spec #:path)) + (root (and=> (assoc-ref outputs output) + (cut assq-ref <> #:path)))) + (and root + (if (string=? path "") + root + (string-append root "/" path))))) + + (define (file-size file) + (stat:size (stat file))) + + (for-each (lambda (spec) + (let* ((build (find-build (assq-ref spec #:job))) + (product (find-product build spec))) + (when (and product (file-exists? 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 spec jobs eval-id) "Build JOBS and return a list of Build results." (define (register job) (let* ((name (assq-ref job #:job-name)) @@ -692,6 +728,8 @@ started)." outputs)) outputs)) (fail (- (length derivations) success))) + + (create-build-outputs results (assq-ref spec #:build-outputs)) (log-message "outputs:\n~a" (string-join outs "\n")) (log-message "success: ~a, fail: ~a" success fail) results)) @@ -777,7 +815,7 @@ started)." (let ((jobs (evaluate store spec eval-id checkouts))) (log-message "building ~a jobs for '~a'" (length jobs) name) - (build-packages store jobs eval-id)))))) + (build-packages store spec jobs eval-id)))))) ;; 'spawn-fiber' returns zero values but we need one. *unspecified*)))) diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm index 82b7e38..a350922 100644 --- a/src/cuirass/database.scm +++ b/src/cuirass/database.scm @@ -1,6 +1,6 @@ ;;; database.scm -- store evaluation and build results ;;; Copyright © 2016, 2017 Mathieu Lirzin -;;; Copyright © 2017 Mathieu Othacehe +;;; Copyright © 2017, 2020 Mathieu Othacehe ;;; Copyright © 2018, 2020 Ludovic Courtès ;;; Copyright © 2018 Clément Lassieur ;;; Copyright © 2018 Tatiana Sholokhova @@ -48,6 +48,7 @@ db-get-pending-derivations build-status db-add-build + db-add-build-product db-update-build-status! db-get-output db-get-inputs @@ -66,6 +67,8 @@ db-get-evaluations-id-min db-get-evaluations-id-max db-get-evaluation-specification + db-get-build-product-path + db-get-build-products db-get-evaluation-summary db-get-checkouts read-sql-file @@ -342,7 +345,8 @@ table." (with-db-worker-thread db (sqlite-exec db "\ INSERT OR IGNORE INTO Specifications (name, load_path_inputs, \ -package_path_inputs, proc_input, proc_file, proc, proc_args) \ +package_path_inputs, proc_input, proc_file, proc, proc_args, \ +build_outputs) \ VALUES (" (assq-ref spec #:name) ", " (assq-ref spec #:load-path-inputs) ", " @@ -350,7 +354,8 @@ package_path_inputs, proc_input, proc_file, proc, proc_args) \ (assq-ref spec #:proc-input) ", " (assq-ref spec #:proc-file) ", " (symbol->string (assq-ref spec #:proc)) ", " - (assq-ref spec #:proc-args) ");") + (assq-ref spec #:proc-args) ", " + (assq-ref spec #:build-outputs) ");") (let ((spec-id (last-insert-rowid db))) (for-each (lambda (input) (db-add-input (assq-ref spec #:name) input)) @@ -394,7 +399,7 @@ DELETE FROM Specifications WHERE name=" name ";") (match rows (() specs) ((#(name load-path-inputs package-path-inputs proc-input proc-file proc - proc-args) + proc-args build-outputs) . rest) (loop rest (cons `((#:name . ,name) @@ -406,7 +411,9 @@ DELETE FROM Specifications WHERE name=" name ";") (#: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))) + (#:inputs . ,(db-get-inputs name)) + (#:build-outputs . + ,(with-input-from-string build-outputs read))) specs))))))) (define (db-add-evaluation spec-name checkouts) @@ -546,6 +553,19 @@ VALUES (" => (sqlite-exec db "ROLLBACK;") #f)))) +(define (db-add-build-product product) + "Insert PRODUCT into BuildProducts table." + (with-db-worker-thread db + (sqlite-exec db "\ +INSERT INTO BuildProducts (build, type, file_size, checksum, +path) VALUES (" + (assq-ref product #:build) ", " + (assq-ref product #:type) ", " + (assq-ref product #:file-size) ", " + (assq-ref product #:checksum) ", " + (assq-ref product #:path) ");") + (last-insert-rowid db))) + (define* (db-update-build-status! drv status #:key log-file) "Update the database so that DRV's status is STATUS. This also updates the 'starttime' or 'stoptime' fields. If LOG-FILE is true, record it as the build @@ -1098,3 +1118,30 @@ AND (" status " IS NULL OR (" status " = 'pending' SELECT specification FROM Evaluations WHERE id = " eval))) (and=> (expect-one-row rows) (cut vector-ref <> 0))))) + +(define (db-get-build-product-path id) + "Return the build product with the given ID." + (with-db-worker-thread db + (let ((rows (sqlite-exec db " +SELECT path FROM BuildProducts +WHERE rowid = " id))) + (and=> (expect-one-row rows) (cut vector-ref <> 0))))) + +(define (db-get-build-products build-id) + "Return the build products associated to the given BUILD-ID." + (with-db-worker-thread db + (let loop ((rows (sqlite-exec db " +SELECT rowid, type, file_size, checksum, path from BuildProducts +WHERE build = " build-id)) + (products '())) + (match rows + (() (reverse products)) + ((#(id type file-size checksum path) + . rest) + (loop rest + (cons `((#:id . ,id) + (#:type . ,type) + (#:file-size . ,file-size) + (#:checksum . ,checksum) + (#:path . ,path)) + products))))))) diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm index c5901f0..79fa246 100644 --- a/src/cuirass/http.scm +++ b/src/cuirass/http.scm @@ -1,6 +1,6 @@ ;;;; http.scm -- HTTP API ;;; Copyright © 2016 Mathieu Lirzin -;;; Copyright © 2017 Mathieu Othacehe +;;; Copyright © 2017, 2020 Mathieu Othacehe ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès ;;; Copyright © 2018 Clément Lassieur ;;; Copyright © 2018 Tatiana Sholokhova @@ -246,17 +246,29 @@ Hydra format." "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") (sxml->xml body port)))) + (define* (respond-file file + #:key name) + (let ((content-type (or (assoc-ref %file-mime-types + (file-extension file)) + '(application/octet-stream)))) + (respond `((content-type . ,content-type) + ,@(if name + `((content-disposition + . (form-data (filename . ,name)))) + '())) + ;; FIXME: FILE is potentially big so it'd be better to not load + ;; it in memory and instead 'sendfile' it. + #:body (call-with-input-file file get-bytevector-all)))) + (define (respond-static-file path) ;; PATH is a list of path components (let ((file-name (string-join path "/")) (file-path (string-join (cons* (%static-directory) path) "/"))) - (if (and (member file-name %file-white-list) + (if (and (member file-name %file-white-list) (file-exists? file-path) (not (file-is-directory? file-path))) - (respond `((content-type . ,(assoc-ref %file-mime-types - (file-extension file-path)))) - #:body (call-with-input-file file-path get-bytevector-all)) - (respond-not-found file-name)))) + (respond-file file-path) + (respond-not-found file-name)))) (define (respond-gzipped-file file) ;; Return FILE with 'gzip' content-encoding. @@ -318,7 +330,8 @@ Hydra format." (#:url . "https://git.savannah.gnu.org/git/guix.git") (#:load-path . ".") (#:branch . ,name) - (#:no-compile? . #t))))) + (#:no-compile? . #t))) + (#:build-outputs . ()))) (respond (build-response #:code 302 #:headers `((location . ,(string->uri-reference "/admin/specifications")))) @@ -352,11 +365,12 @@ Hydra format." (respond-json (object->json-string hydra-build)) (respond-build-not-found id)))) (('GET "build" build-id "details") - (let ((build (db-get-build (string->number build-id)))) + (let ((build (db-get-build (string->number build-id))) + (products (db-get-build-products build-id))) (if build (respond-html (html-page (string-append "Build " build-id) - (build-details build) + (build-details build products) `(((#:name . ,(assq-ref build #:specification)) (#:link . ,(string-append "/jobset/" (assq-ref build #:specification))))))) (respond-build-not-found build-id)))) @@ -505,6 +519,10 @@ Hydra format." query)) (respond-json-with-error 500 "Query parameter not provided!")))) + (('GET "download" id) + (let ((path (db-get-build-product-path id))) + (respond-file path #:name (basename path)))) + (('GET "static" path ...) (respond-static-file path)) (_ diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm index 4104c7b..600d9d8 100644 --- a/src/cuirass/templates.scm +++ b/src/cuirass/templates.scm @@ -2,6 +2,7 @@ ;;; Copyright © 2018 Tatiana Sholokhova ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès ;;; Copyright © 2019, 2020 Ricardo Wurmus +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -27,6 +28,7 @@ #:use-module (srfi srfi-26) #:use-module (web uri) #:use-module (guix derivations) + #:use-module (guix progress) #:use-module (guix store) #:use-module ((guix utils) #:select (string-replace-substring)) #:use-module ((cuirass database) #:select (build-status)) @@ -212,7 +214,7 @@ system whose names start with " (code "guile-") ":" (br) "Add"))))) '())))) -(define (build-details build) +(define (build-details build products) "Return HTML showing details for the BUILD." (define status (assq-ref build #:status)) (define blocking-outputs @@ -282,7 +284,38 @@ system whose names start with " (code "guile-") ":" (br) (tr (th "Outputs") (td ,(map (match-lambda ((out (#:path . path)) `(pre ,path))) - (assq-ref build #:outputs)))))))) + (assq-ref build #:outputs)))) + ,@(if (null? products) + '() + (let ((product-items + (map + (lambda (product) + (let* ((id (assq-ref product #:id)) + (size (assq-ref product #:file-size)) + (type (assq-ref product #:type)) + (path (assq-ref product #:path)) + (href (format #f "/download/~a" id))) + `(a (@ (href ,href)) + (li (@ (class "list-group-item")) + (div + (@ (class "container")) + (div + (@ (class "row")) + (div + (@ (class "col-md-auto")) + (span + (@ (class "oi oi-data-transfer-download") + (title "Download") + (aria-hidden "true")))) + (div (@ (class "col-md-auto")) + ,path) + (div (@ (class "col-md-auto")) + "(" ,(byte-count->string size) ")"))))))) + products))) + `((tr (th "Build outputs") + (td + (ul (@ (class "list-group d-flex flex-row")) + ,product-items)))))))))) (define (pagination first-link prev-link next-link last-link) "Return html page navigation buttons with LINKS." diff --git a/src/schema.sql b/src/schema.sql index 1104551..8ae0a85 100644 --- a/src/schema.sql +++ b/src/schema.sql @@ -7,7 +7,8 @@ CREATE TABLE Specifications ( 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 + proc_args TEXT NOT NULL, -- passed to proc + build_outputs TEXT NOT NULL --specify what build outputs should be made available for download ); CREATE TABLE Inputs ( @@ -65,6 +66,16 @@ CREATE TABLE Builds ( FOREIGN KEY (evaluation) REFERENCES Evaluations (id) ); +CREATE TABLE BuildProducts ( + build INTEGER NOT NULL, + type TEXT NOT NULL, + file_size BIGINT NOT NULL, + checksum TEXT NOT NULL, + path TEXT NOT NULL, + PRIMARY KEY (build, path) + FOREIGN KEY (build) REFERENCES Builds (id) ON DELETE CASCADE +); + CREATE TABLE Events ( id INTEGER PRIMARY KEY, type TEXT NOT NULL, diff --git a/src/sql/upgrade-7.sql b/src/sql/upgrade-7.sql new file mode 100644 index 0000000..b9bd4ff --- /dev/null +++ b/src/sql/upgrade-7.sql @@ -0,0 +1,15 @@ +BEGIN TRANSACTION; + +CREATE TABLE BuildProducts ( + build INTEGER NOT NULL, + type TEXT NOT NULL, + file_size BIGINT NOT NULL, + checksum TEXT NOT NULL, + path TEXT NOT NULL, + PRIMARY KEY (build, path) + FOREIGN KEY (build) REFERENCES Builds (id) ON DELETE CASCADE +); + +ALTER TABLE Specifications ADD build_outputs TEXT NOT NULL DEFAULT "()"; + +COMMIT; diff --git a/tests/database.scm b/tests/database.scm index 6098465..98b5012 100644 --- a/tests/database.scm +++ b/tests/database.scm @@ -3,6 +3,7 @@ ;;; Copyright © 2016 Mathieu Lirzin ;;; Copyright © 2018 Ludovic Courtès ;;; Copyright © 2018 Clément Lassieur +;;; Copyright © 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -45,7 +46,8 @@ (#:branch . "master") (#:tag . #f) (#:commit . #f) - (#:no-compile? . #f)))))) + (#:no-compile? . #f)))) + (#:build-outputs . ()))) (define (make-dummy-checkouts fakesha1 fakesha2) `(((#:commit . ,fakesha1) diff --git a/tests/http.scm b/tests/http.scm index d20a3c3..d69c25c 100644 --- a/tests/http.scm +++ b/tests/http.scm @@ -1,7 +1,7 @@ ;;; http.scm -- tests for (cuirass http) module ;;; Copyright © 2016 Mathieu Lirzin ;;; Copyright © 2017, 2018, 2019, 2020 Ludovic Courtès -;;; Copyright © 2017 Mathieu Othacehe +;;; Copyright © 2017, 2020 Mathieu Othacehe ;;; Copyright © 2018 Clément Lassieur ;;; ;;; This file is part of Cuirass. @@ -170,7 +170,8 @@ (#:branch . "master") (#:tag . #f) (#:commit . #f) - (#:no-compile? . #f)))))) + (#:no-compile? . #f)))) + (#:build-outputs . ()))) (checkouts1 '(((#:commit . "fakesha1") (#:input . "savannah")