derivations: Pass the derivation of guile-for-build to `imported-files' & co.
[jackhill/guix/guix.git] / guix / derivations.scm
index b7733e9..4be3168 100644 (file)
   #:use-module (ice-9 rdelim)
   #:use-module (guix store)
   #:use-module (guix utils)
-  #:export (derivation?
+  #:export (<derivation>
+            derivation?
             derivation-outputs
             derivation-inputs
             derivation-sources
             derivation-system
             derivation-builder-arguments
             derivation-builder-environment-vars
+            derivation-prerequisites
+            derivation-prerequisites-to-build
 
+            <derivation-output>
             derivation-output?
             derivation-output-path
             derivation-output-hash-algo
             derivation-output-hash
 
+            <derivation-input>
             derivation-input?
             derivation-input-path
             derivation-input-sub-derivations
@@ -52,7 +57,8 @@
             derivation
 
             %guile-for-build
-            build-expression->derivation))
+            build-expression->derivation
+            imported-files))
 
 ;;;
 ;;; Nix derivations, as implemented in Nix's `derivations.cc'.
@@ -91,6 +97,42 @@ download with a fixed hash (aka. `fetchurl')."
      #t)
     (_ #f)))
 
+(define (derivation-prerequisites drv)
+  "Return the list of derivation-inputs required to build DRV, recursively."
+  (let loop ((drv    drv)
+             (result '()))
+    (let ((inputs (remove (cut member <> result)  ; XXX: quadratic
+                          (derivation-inputs drv))))
+      (fold loop
+            (append inputs result)
+            (map (lambda (i)
+                   (call-with-input-file (derivation-input-path i)
+                     read-derivation))
+                 inputs)))))
+
+(define (derivation-prerequisites-to-build store drv)
+  "Return the list of derivation-inputs required to build DRV and not already
+available in STORE, recursively."
+  (define input-built?
+    (match-lambda
+     (($ <derivation-input> path sub-drvs)
+      (let ((out (map (cut derivation-path->output-path path <>)
+                      sub-drvs)))
+        (any (cut valid-path? store <>) out)))))
+
+  (let loop ((drv    drv)
+             (result '()))
+    (let ((inputs (remove (lambda (i)
+                            (or (member i result) ; XXX: quadratic
+                                (input-built? i)))
+                          (derivation-inputs drv))))
+      (fold loop
+            (append inputs result)
+            (map (lambda (i)
+                   (call-with-input-file (derivation-input-path i)
+                     read-derivation))
+                 inputs)))))
+
 (define (read-derivation drv-port)
   "Read the derivation from DRV-PORT and return the corresponding
 <derivation> object."
@@ -129,6 +171,10 @@ download with a fixed hash (aka. `fetchurl')."
                 '()
                 x))
 
+  ;; The contents of a derivation are typically ASCII, but choosing
+  ;; UTF-8 allows us to take the fast path for Guile's `scm_getc'.
+  (set-port-encoding! drv-port "UTF-8")
+
   (let loop ((exp    (read drv-port))
              (result '()))
     (match exp
@@ -158,12 +204,42 @@ download with a fixed hash (aka. `fetchurl')."
   "Write the ATerm-like serialization of DRV to PORT.  See Section 2.4 of
 Eelco Dolstra's PhD dissertation for an overview of a previous version of
 that form."
+
+  ;; Make sure we're using the faster implementation.
+  (define format simple-format)
+
   (define (list->string lst)
     (string-append "[" (string-join lst ",") "]"))
 
   (define (write-list lst)
     (display (list->string lst) port))
 
+  (define (coalesce-duplicate-inputs inputs)
+    ;; Return a list of inputs, such that when INPUTS contains the same DRV
+    ;; twice, they are coalesced, with their sub-derivations merged.  This is
+    ;; needed because Nix itself keeps only one of them.
+    (fold (lambda (input result)
+            (match input
+              (($ <derivation-input> path sub-drvs)
+               ;; XXX: quadratic
+               (match (find (match-lambda
+                             (($ <derivation-input> p s)
+                              (string=? p path)))
+                            result)
+                 (#f
+                  (cons input result))
+                 ((and dup ($ <derivation-input> _ sub-drvs2))
+                  ;; Merge DUP with INPUT.
+                  (let ((sub-drvs (delete-duplicates
+                                   (append sub-drvs sub-drvs2))))
+                    (cons (make-derivation-input path sub-drvs)
+                          (delq dup result))))))))
+          '()
+          inputs))
+
+  ;; Note: lists are sorted alphabetically, to conform with the behavior of
+  ;; C++ `std::map' in Nix itself.
+
   (match drv
     (($ <derivation> outputs inputs sources
         system builder args env-vars)
@@ -175,30 +251,41 @@ that form."
                                 (or (and=> hash-algo symbol->string) "")
                                 (or (and=> hash bytevector->base16-string)
                                     ""))))
-                      outputs))
+                      (sort outputs
+                            (lambda (o1 o2)
+                              (string<? (car o1) (car o2))))))
      (display "," port)
      (write-list (map (match-lambda
                        (($ <derivation-input> path sub-drvs)
                         (format #f "(~s,~a)" path
-                                (list->string (map object->string sub-drvs)))))
-                      inputs))
+                                (list->string (map object->string
+                                                   (sort sub-drvs string<?))))))
+                      (sort (coalesce-duplicate-inputs inputs)
+                            (lambda (i1 i2)
+                              (string<? (derivation-input-path i1)
+                                        (derivation-input-path i2))))))
      (display "," port)
-     (write-list (map object->string sources))
+     (write-list (map object->string (sort sources string<?)))
      (format port ",~s,~s," system builder)
      (write-list (map object->string args))
      (display "," port)
      (write-list (map (match-lambda
                        ((name . value)
                         (format #f "(~s,~s)" name value)))
-                      env-vars))
+                      (sort env-vars
+                            (lambda (e1 e2)
+                              (string<? (car e1) (car e2))))))
      (display ")" port))))
 
-(define* (derivation-path->output-path path #:optional (output "out"))
-  "Read the derivation from PATH (`/nix/store/xxx.drv'), and return the store
+(define derivation-path->output-path
+  ;; This procedure is called frequently, so memoize it.
+  (memoize
+   (lambda* (path #:optional (output "out"))
+     "Read the derivation from PATH (`/nix/store/xxx.drv'), and return the store
 path of its output OUTPUT."
-  (let* ((drv     (call-with-input-file path read-derivation))
-         (outputs (derivation-outputs drv)))
-    (and=> (assoc-ref outputs output) derivation-output-path)))
+     (let* ((drv     (call-with-input-file path read-derivation))
+            (outputs (derivation-outputs drv)))
+       (and=> (assoc-ref outputs output) derivation-output-path)))))
 
 \f
 ;;;
@@ -236,20 +323,15 @@ in SIZE bytes."
           system builder args env-vars)
        ;; A regular derivation: replace the path of each input with that
        ;; input's hash; return the hash of serialization of the resulting
-       ;; derivation.  Note: inputs are sorted as in the order of their hex
-       ;; hash representation because that's what the C++ `std::map' code
-       ;; does.
-       (let* ((inputs (sort (map (match-lambda
-                                  (($ <derivation-input> path sub-drvs)
-                                   (let ((hash (call-with-input-file path
-                                                 (compose bytevector->base16-string
-                                                          derivation-hash
-                                                          read-derivation))))
-                                     (make-derivation-input hash sub-drvs))))
-                                 inputs)
-                            (lambda (i1 i2)
-                              (string<? (derivation-input-path i1)
-                                        (derivation-input-path i2)))))
+       ;; derivation.
+       (let* ((inputs (map (match-lambda
+                            (($ <derivation-input> path sub-drvs)
+                             (let ((hash (call-with-input-file path
+                                           (compose bytevector->base16-string
+                                                    derivation-hash
+                                                    read-derivation))))
+                               (make-derivation-input hash sub-drvs))))
+                           inputs))
               (drv    (make-derivation outputs inputs sources
                                        system builder args env-vars)))
          (sha256
@@ -315,12 +397,12 @@ known in advance, such as a file download."
                         (cons name "")
                         (cons name val))))
                   env-vars)))
-      (fold-right (lambda (output-name env-vars)
-                    (if (assoc output-name env-vars)
-                        env-vars
-                        (append env-vars `((,output-name . "")))))
-                  e
-                  outputs)))
+      (fold (lambda (output-name env-vars)
+              (if (assoc output-name env-vars)
+                  env-vars
+                  (append env-vars `((,output-name . "")))))
+            e
+            outputs)))
 
   (let* ((outputs    (map (lambda (name)
                             ;; Return outputs with an empty path.
@@ -338,7 +420,7 @@ known in advance, such as a file download."
                                                       (hash-algo sha256) #t #t
                                                       input)))
                               (make-derivation-input path '()))))
-                          inputs))
+                          (delete-duplicates inputs)))
          (env-vars   (env-vars-with-empty-outputs))
          (drv-masked (make-derivation outputs
                                       (filter (compose derivation-path?
@@ -352,6 +434,8 @@ known in advance, such as a file download."
                                       system builder args env-vars))
          (drv        (add-output-paths drv-masked)))
 
+    ;; (write-derivation drv-masked (current-error-port))
+    ;; (newline (current-error-port))
     (values (add-text-to-store store (string-append name ".drv")
                                (call-with-output-string
                                 (cut write-derivation drv <>))
@@ -367,33 +451,219 @@ known in advance, such as a file download."
 (define %guile-for-build
   ;; The derivation of the Guile to be used within the build environment,
   ;; when using `build-expression->derivation'.
-  (make-parameter (false-if-exception (nixpkgs-derivation "guile"))))
+  (make-parameter #f))
+
+(define (parent-directories file-name)
+  "Return the list of parent dirs of FILE-NAME, in the order in which an
+`mkdir -p' implementation would make them."
+  (let ((not-slash (char-set-complement (char-set #\/))))
+    (reverse
+     (fold (lambda (dir result)
+             (match result
+               (()
+                (list dir))
+               ((prev _ ...)
+                (cons (string-append prev "/" dir)
+                      result))))
+           '()
+           (remove (cut string=? <> ".")
+                   (string-tokenize (dirname file-name) not-slash))))))
+
+(define* (imported-files store files
+                         #:key (name "file-import")
+                         (system (%current-system))
+                         (guile (%guile-for-build)))
+  "Return a derivation that imports FILES into STORE.  FILES must be a list
+of (FINAL-PATH . FILE-NAME) pairs; each FILE-NAME is read from the file
+system, imported, and appears under FINAL-PATH in the resulting store path."
+  (let* ((files   (map (match-lambda
+                        ((final-path . file-name)
+                         (list final-path
+                               (add-to-store store (basename final-path) #t #f
+                                             "sha256" file-name))))
+                       files))
+         (builder
+          `(begin
+             (mkdir %output) (chdir %output)
+             ,@(append-map (match-lambda
+                            ((final-path store-path)
+                             (append (match (parent-directories final-path)
+                                       (() '())
+                                       ((head ... tail)
+                                        (append (map (lambda (d)
+                                                       `(false-if-exception
+                                                         (mkdir ,d)))
+                                                     head)
+                                                `((or (file-exists? ,tail)
+                                                      (mkdir ,tail))))))
+                                     `((symlink ,store-path ,final-path)))))
+                           files))))
+    (build-expression->derivation store name (%current-system)
+                                  builder files
+                                  #:guile-for-build guile)))
+
+(define* (imported-modules store modules
+                           #:key (name "module-import")
+                           (system (%current-system))
+                           (guile (%guile-for-build)))
+  "Return a derivation that contains the source files of MODULES, a list of
+module names such as `(ice-9 q)'.  All of MODULES must be in the current
+search path."
+  ;; TODO: Determine the closure of MODULES, build the `.go' files,
+  ;; canonicalize the source files through read/write, etc.
+  (let ((files (map (lambda (m)
+                      (let ((f (string-append
+                                (string-join (map symbol->string m) "/")
+                                ".scm")))
+                        (cons f (search-path %load-path f))))
+                    modules)))
+    (imported-files store files #:name name #:system system
+                    #:guile guile)))
+
+(define* (compiled-modules store modules
+                           #:key (name "module-import-compiled")
+                           (system (%current-system))
+                           (guile (%guile-for-build)))
+  "Return a derivation that builds a tree containing the `.go' files
+corresponding to MODULES.  All the MODULES are built in a context where
+they can refer to each other."
+  (let* ((module-drv (imported-modules store modules
+                                       #:system system
+                                       #:guile guile))
+         (module-dir (derivation-path->output-path module-drv))
+         (files      (map (lambda (m)
+                            (let ((f (string-join (map symbol->string m)
+                                                  "/")))
+                              (cons (string-append f ".go")
+                                    (string-append module-dir "/" f ".scm"))))
+                      modules)))
+    (define builder
+      `(begin
+         (use-modules (system base compile))
+         (let ((out (assoc-ref %outputs "out")))
+           (mkdir out)
+           (chdir out))
+
+         (set! %load-path
+               (cons ,module-dir %load-path))
+
+         ,@(map (match-lambda
+                 ((output . input)
+                  (let ((make-parent-dirs (map (lambda (dir)
+                                                 `(unless (file-exists? ,dir)
+                                                    (mkdir ,dir)))
+                                               (parent-directories output))))
+                   `(begin
+                      ,@make-parent-dirs
+                      (compile-file ,input
+                                    #:output-file ,output
+                                    #:opts %auto-compilation-options)))))
+                files)))
+
+    (build-expression->derivation store name system builder
+                                  `(("modules" ,module-drv))
+                                  #:guile-for-build guile)))
 
 (define* (build-expression->derivation store name system exp inputs
-                                       #:key hash hash-algo)
+                                       #:key (outputs '("out"))
+                                       hash hash-algo
+                                       (env-vars '())
+                                       (modules '())
+                                       guile-for-build)
   "Return a derivation that executes Scheme expression EXP as a builder for
-derivation NAME.  INPUTS must be a list of string/derivation-path pairs.  EXP
-is evaluated in an environment where %OUTPUT is bound to the output path, and
-where %BUILD-INPUTS is bound to an alist of string/output-path pairs made
-from INPUTS."
+derivation NAME.  INPUTS must be a list of (NAME DRV-PATH SUB-DRV) tuples;
+when SUB-DRV is omitted, \"out\" is assumed.  EXP is evaluated in an
+environment where %OUTPUT is bound to the main output path, %OUTPUTS is bound
+to a list of output/path pairs, and where %BUILD-INPUTS is bound to an alist
+of string/output-path pairs made from INPUTS.  Optionally, ENV-VARS is a list
+of string pairs specifying the name and value of environment variables
+visible to the builder.  The builder terminates by passing the result of EXP
+to `exit'; thus, when EXP returns #f, the build is considered to have
+failed.
+
+EXP is built using GUILE-FOR-BUILD (a derivation).  When GUILE-FOR-BUILD is
+omitted or is #f, the value of the `%guile-for-build' fluid is used instead."
+  (define guile-drv
+    (or guile-for-build (%guile-for-build)))
+
   (define guile
-    (string-append (derivation-path->output-path (%guile-for-build))
+    (string-append (derivation-path->output-path guile-drv)
                    "/bin/guile"))
 
+  (define module-form?
+    (match-lambda
+      (((or 'define-module 'use-modules) _ ...) #t)
+      (_ #f)))
+
   (let* ((prologue `(begin
+                      ,@(match exp
+                          ((_ ...)
+                           ;; Module forms must appear at the top-level so
+                           ;; that any macros they export can be expanded.
+                           (filter module-form? exp))
+                          (_ `(,exp)))
+
                       (define %output (getenv "out"))
+                      (define %outputs
+                        (map (lambda (o)
+                               (cons o (getenv o)))
+                             ',outputs))
                       (define %build-inputs
                         ',(map (match-lambda
-                                ((name . drv)
-                                 (cons name
-                                       (derivation-path->output-path drv))))
-                               inputs))) )
+                                ((name drv . rest)
+                                 (let ((sub (match rest
+                                              (() "out")
+                                              ((x) x))))
+                                   (cons name
+                                         (if (derivation-path? drv)
+                                             (derivation-path->output-path drv
+                                                                           sub)
+                                             drv)))))
+                               inputs))
+
+                      ,@(if (null? modules)
+                            '()
+                            ;; Remove our own settings.
+                            '((unsetenv "GUILE_LOAD_COMPILED_PATH")))
+
+                      ;; Guile sets it, but remove it to avoid conflicts when
+                      ;; building Guile-using packages.
+                      (unsetenv "LD_LIBRARY_PATH")))
          (builder  (add-text-to-store store
                                       (string-append name "-guile-builder")
-                                      (string-append (object->string prologue)
-                                                     (object->string exp))
-                                      (map cdr inputs))))
-    (derivation store name system guile `("--no-auto-compile" ,builder)
-                '(("HOME" . "/homeless"))
-                `((,(%guile-for-build))
-                  (,builder)))))
+                                      (string-append
+                                       (object->string prologue)
+                                       (object->string
+                                        `(exit
+                                          ,(match exp
+                                             ((_ ...)
+                                              (remove module-form? exp))
+                                             (_ `(,exp))))))
+                                      (map second inputs)))
+         (mod-drv  (and (pair? modules)
+                        (imported-modules store modules #:guile guile-drv)))
+         (mod-dir  (and mod-drv
+                        (derivation-path->output-path mod-drv)))
+         (go-drv   (and (pair? modules)
+                        (compiled-modules store modules #:guile guile-drv)))
+         (go-dir   (and go-drv
+                        (derivation-path->output-path go-drv))))
+    (derivation store name system guile
+                `("--no-auto-compile"
+                  ,@(if mod-dir `("-L" ,mod-dir) '())
+                  ,builder)
+
+                ;; When MODULES is non-empty, shamelessly clobber
+                ;; $GUILE_LOAD_COMPILED_PATH.
+                (if go-dir
+                    `(("GUILE_LOAD_COMPILED_PATH" . ,go-dir)
+                      ,@(alist-delete "GUILE_LOAD_COMPILED_PATH"
+                                      env-vars))
+                    env-vars)
+
+                `((,(or guile-for-build (%guile-for-build)))
+                  (,builder)
+                  ,@(map cdr inputs)
+                  ,@(if mod-drv `((,mod-drv) (,go-drv)) '()))
+                #:hash hash #:hash-algo hash-algo
+                #:outputs outputs)))