summaryrefslogtreecommitdiff
path: root/guix/gexp.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guix/gexp.scm')
-rw-r--r--guix/gexp.scm391
1 files changed, 391 insertions, 0 deletions
diff --git a/guix/gexp.scm b/guix/gexp.scm
new file mode 100644
index 0000000000..9dd83f5370
--- /dev/null
+++ b/guix/gexp.scm
@@ -0,0 +1,391 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2014 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; 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 (guix gexp)
+ #:use-module ((guix store)
+ #:select (direct-store-path?))
+ #:use-module (guix monads)
+ #:use-module ((guix derivations)
+ #:select (derivation? derivation->output-path
+ %guile-for-build derivation))
+ #:use-module (guix packages)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (ice-9 match)
+ #:export (gexp
+ gexp?
+ gexp->derivation
+ gexp->file
+ gexp->script))
+
+;;; Commentary:
+;;;
+;;; This module implements "G-expressions", or "gexps". Gexps are like
+;;; S-expressions (sexps), with two differences:
+;;;
+;;; 1. References (un-quotations) to derivations or packages in a gexp are
+;;; replaced by the corresponding output file name;
+;;;
+;;; 2. Gexps embed information about the derivations they refer to.
+;;;
+;;; Gexps make it easy to write to files Scheme code that refers to store
+;;; items, or to write Scheme code to build derivations.
+;;;
+;;; Code:
+
+;; "G expressions".
+(define-record-type <gexp>
+ (make-gexp references proc)
+ gexp?
+ (references gexp-references) ; ((DRV-OR-PKG OUTPUT) ...)
+ (proc gexp-proc)) ; procedure
+
+;; Reference to one of the derivation's outputs, for gexps used in
+;; derivations.
+(define-record-type <output-ref>
+ (output-ref name)
+ output-ref?
+ (name output-ref-name))
+
+(define raw-derivation
+ (store-lift derivation))
+
+(define (lower-inputs* inputs)
+ "Turn any package from INPUTS into a derivation; return the corresponding
+input list as a monadic value."
+ ;; XXX: This is like 'lower-inputs' but without the "name" part in tuples.
+ (with-monad %store-monad
+ (sequence %store-monad
+ (map (match-lambda
+ (((? package? package) sub-drv ...)
+ (mlet %store-monad ((drv (package->derivation package)))
+ (return `(,drv ,@sub-drv))))
+ (input
+ (return input)))
+ inputs))))
+
+(define* (gexp->derivation name exp
+ #:key
+ (system (%current-system))
+ hash hash-algo recursive?
+ (env-vars '())
+ (modules '())
+ (guile-for-build (%guile-for-build))
+ references-graphs
+ local-build?)
+ "Return a derivation NAME that runs EXP (a gexp) with GUILE-FOR-BUILD (a
+derivation) on SYSTEM.
+
+Make MODULES available in the evaluation context of EXP; MODULES is a list of
+names of Guile modules from the current search path to be copied in the store,
+compiled, and made available in the load path during the execution of
+EXP---e.g., '((guix build utils) (guix build gnu-build-system)).
+
+The other arguments are as for 'derivation'."
+ (define %modules modules)
+ (define outputs (gexp-outputs exp))
+
+ (mlet* %store-monad ((inputs (lower-inputs* (gexp-inputs exp)))
+ (sexp (gexp->sexp exp #:outputs outputs))
+ (builder (text-file (string-append name "-builder")
+ (object->string sexp)))
+ (modules (if (pair? %modules)
+ (imported-modules %modules
+ #:system system
+ #:guile guile-for-build)
+ (return #f)))
+ (compiled (if (pair? %modules)
+ (compiled-modules %modules
+ #:system system
+ #:guile guile-for-build)
+ (return #f)))
+ (guile (if guile-for-build
+ (return guile-for-build)
+ (package->derivation
+ (@ (gnu packages base) guile-final)
+ system))))
+ (raw-derivation name
+ (string-append (derivation->output-path guile)
+ "/bin/guile")
+ `("--no-auto-compile"
+ ,@(if (pair? %modules)
+ `("-L" ,(derivation->output-path modules)
+ "-C" ,(derivation->output-path compiled))
+ '())
+ ,builder)
+ #:outputs outputs
+ #:env-vars env-vars
+ #:system system
+ #:inputs `((,guile)
+ (,builder)
+ ,@(if modules
+ `((,modules) (,compiled) ,@inputs)
+ inputs))
+ #:hash hash #:hash-algo hash-algo #:recursive? recursive?
+ #:references-graphs references-graphs
+ #:local-build? local-build?)))
+
+(define (gexp-inputs exp)
+ "Return the input list for EXP."
+ (define (add-reference-inputs ref result)
+ (match ref
+ (((? derivation?) (? string?))
+ (cons ref result))
+ (((? package?) (? string?))
+ (cons ref result))
+ ((? gexp? exp)
+ (append (gexp-inputs exp) result))
+ (((? string? file))
+ (if (direct-store-path? file)
+ (cons ref result)
+ result))
+ ((refs ...)
+ (fold-right add-reference-inputs result refs))
+ (_
+ ;; Ignore references to other kinds of objects.
+ result)))
+
+ (fold-right add-reference-inputs
+ '()
+ (gexp-references exp)))
+
+(define (gexp-outputs exp)
+ "Return the outputs referred to by EXP as a list of strings."
+ (define (add-reference-output ref result)
+ (match ref
+ (($ <output-ref> name)
+ (cons name result))
+ ((? gexp? exp)
+ (append (gexp-outputs exp) result))
+ (_
+ result)))
+
+ (fold-right add-reference-output
+ '()
+ (gexp-references exp)))
+
+(define* (gexp->sexp exp #:key (outputs '()))
+ "Return (monadically) the sexp corresponding to EXP for the given OUTPUT,
+and in the current monad setting (system type, etc.)"
+ (define (reference->sexp ref)
+ (with-monad %store-monad
+ (match ref
+ (((? derivation? drv) (? string? output))
+ (return (derivation->output-path drv output)))
+ (((? package? p) (? string? output))
+ (package-file p #:output output))
+ (($ <output-ref> output)
+ (match (member output outputs)
+ (#f
+ (error "no such output" output))
+ (_
+ (return `((@ (guile) getenv) ,output)))))
+ ((? gexp? exp)
+ (gexp->sexp exp #:outputs outputs))
+ (((? string? str))
+ (return (if (direct-store-path? str) str ref)))
+ ((refs ...)
+ (sequence %store-monad (map reference->sexp refs)))
+ (x
+ (return x)))))
+
+ (mlet %store-monad
+ ((args (sequence %store-monad
+ (map reference->sexp (gexp-references exp)))))
+ (return (apply (gexp-proc exp) args))))
+
+(define (canonicalize-reference ref)
+ "Return a canonical variant of REF, which adds any missing output part in
+package/derivation references."
+ (match ref
+ ((? package? p)
+ `(,p "out"))
+ ((? derivation? d)
+ `(,d "out"))
+ (((? package?) (? string?))
+ ref)
+ (((? derivation?) (? string?))
+ ref)
+ ((? string? s)
+ (if (direct-store-path? s) `(,s) s))
+ ((refs ...)
+ (map canonicalize-reference refs))
+ (x x)))
+
+(define (syntax-location-string s)
+ "Return a string representing the source code location of S."
+ (let ((props (syntax-source s)))
+ (if props
+ (let ((file (assoc-ref props 'filename))
+ (line (and=> (assoc-ref props 'line) 1+))
+ (column (assoc-ref props 'column)))
+ (if file
+ (simple-format #f "~a:~a:~a"
+ file line column)
+ (simple-format #f "~a:~a" line column)))
+ "<unknown location>")))
+
+(define-syntax gexp
+ (lambda (s)
+ (define (collect-escapes exp)
+ ;; Return all the 'ungexp' present in EXP.
+ (let loop ((exp exp)
+ (result '()))
+ (syntax-case exp (ungexp ungexp-splicing)
+ ((ungexp _)
+ (cons exp result))
+ ((ungexp _ _)
+ (cons exp result))
+ ((ungexp-splicing _ ...)
+ (cons exp result))
+ ((exp0 exp ...)
+ (let ((result (loop #'exp0 result)))
+ (fold loop result #'(exp ...))))
+ (_
+ result))))
+
+ (define (escape->ref exp)
+ ;; Turn 'ungexp' form EXP into a "reference".
+ (syntax-case exp (ungexp ungexp-splicing output)
+ ((ungexp output)
+ #'(output-ref "out"))
+ ((ungexp output name)
+ #'(output-ref name))
+ ((ungexp thing)
+ #'thing)
+ ((ungexp drv-or-pkg out)
+ #'(list drv-or-pkg out))
+ ((ungexp-splicing lst)
+ #'lst)))
+
+ (define (substitute-references exp substs)
+ ;; Return a variant of EXP where all the cars of SUBSTS have been
+ ;; replaced by the corresponding cdr.
+ (syntax-case exp (ungexp ungexp-splicing)
+ ((ungexp _ ...)
+ (match (assoc exp substs)
+ ((_ id)
+ id)
+ (_
+ #'(syntax-error "error: no 'ungexp' substitution"
+ #'ref))))
+ (((ungexp-splicing _ ...) rest ...)
+ (syntax-case exp ()
+ ((exp rest ...)
+ (match (assoc #'exp substs)
+ ((_ id)
+ (with-syntax ((id id))
+ #`(append id
+ #,(substitute-references #'(rest ...) substs))))
+ (_
+ #'(syntax-error "error: no 'ungexp-splicing' substitution"
+ #'ref))))))
+ ((exp0 exp ...)
+ #`(cons #,(substitute-references #'exp0 substs)
+ #,(substitute-references #'(exp ...) substs)))
+ (x #''x)))
+
+ (syntax-case s (ungexp output)
+ ((_ exp)
+ (let* ((escapes (delete-duplicates (collect-escapes #'exp)))
+ (formals (generate-temporaries escapes))
+ (sexp (substitute-references #'exp (zip escapes formals)))
+ (refs (map escape->ref escapes)))
+ #`(make-gexp (map canonicalize-reference (list #,@refs))
+ (lambda #,formals
+ #,sexp)))))))
+
+
+;;;
+;;; Convenience procedures.
+;;;
+
+(define* (gexp->script name exp
+ #:key (modules '())
+ (guile (@ (gnu packages base) guile-final)))
+ "Return an executable script NAME that runs EXP using GUILE with MODULES in
+its search path."
+ (mlet %store-monad ((modules (imported-modules modules))
+ (compiled (compiled-modules modules)))
+ (gexp->derivation name
+ (gexp
+ (call-with-output-file (ungexp output)
+ (lambda (port)
+ (format port
+ "#!~a/bin/guile --no-auto-compile~%!#~%"
+ (ungexp guile))
+ (write
+ '(set! %load-path
+ (cons (ungexp modules) %load-path))
+ port)
+ (write
+ '(set! %load-compiled-path
+ (cons (ungexp compiled)
+ %load-compiled-path))
+ port)
+ (write '(ungexp exp) port)
+ (chmod port #o555)))))))
+
+(define (gexp->file name exp)
+ "Return a derivation that builds a file NAME containing EXP."
+ (gexp->derivation name
+ (gexp
+ (call-with-output-file (ungexp output)
+ (lambda (port)
+ (write '(ungexp exp) port))))))
+
+
+
+;;;
+;;; Syntactic sugar.
+;;;
+
+(eval-when (expand load eval)
+ (define (read-ungexp chr port)
+ "Read an 'ungexp' or 'ungexp-splicing' form from PORT."
+ (define unquote-symbol
+ (match (peek-char port)
+ (#\@
+ (read-char port)
+ 'ungexp-splicing)
+ (_
+ 'ungexp)))
+
+ (match (read port)
+ ((? symbol? symbol)
+ (let ((str (symbol->string symbol)))
+ (match (string-index-right str #\:)
+ (#f
+ `(,unquote-symbol ,symbol))
+ (colon
+ (let ((name (string->symbol (substring str 0 colon)))
+ (output (substring str (+ colon 1))))
+ `(,unquote-symbol ,name ,output))))))
+ (x
+ `(,unquote-symbol ,x))))
+
+ (define (read-gexp chr port)
+ "Read a 'gexp' form from PORT."
+ `(gexp ,(read port)))
+
+ ;; Extend the reader
+ (read-hash-extend #\~ read-gexp)
+ (read-hash-extend #\$ read-ungexp))
+
+;;; gexp.scm ends here