profiles: Store search paths in manifests.
[jackhill/guix/guix.git] / guix / profiles.scm
index a2f63d1..2e515d5 100644 (file)
@@ -59,6 +59,7 @@
             manifest-entry-output
             manifest-entry-item
             manifest-entry-dependencies
+            manifest-entry-search-paths
 
             manifest-pattern
             manifest-pattern?
@@ -78,6 +79,7 @@
 
             profile-manifest
             package->manifest-entry
+            %default-profile-hooks
             profile-derivation
             generation-number
             generation-numbers
                 (default "out"))
   (item         manifest-entry-item)              ; package | store path
   (dependencies manifest-entry-dependencies       ; (store path | package)*
+                (default '()))
+  (search-paths manifest-entry-search-paths       ; search-path-specification*
                 (default '())))
 
 (define-record-type* <manifest-pattern> manifest-pattern
@@ -164,25 +168,60 @@ omitted or #f, use the first output of PACKAGE."
      (version (package-version package))
      (output (or output (car (package-outputs package))))
      (item package)
-     (dependencies (delete-duplicates deps)))))
+     (dependencies (delete-duplicates deps))
+     (search-paths (package-native-search-paths package)))))
 
 (define (manifest->gexp manifest)
   "Return a representation of MANIFEST as a gexp."
   (define (entry->gexp entry)
     (match entry
-      (($ <manifest-entry> name version output (? string? path) (deps ...))
-       #~(#$name #$version #$output #$path #$deps))
-      (($ <manifest-entry> name version output (? package? package) (deps ...))
+      (($ <manifest-entry> name version output (? string? path)
+                           (deps ...) (search-paths ...))
+       #~(#$name #$version #$output #$path
+                 (propagated-inputs #$deps)
+                 (search-paths #$(map search-path-specification->sexp
+                                      search-paths))))
+      (($ <manifest-entry> name version output (? package? package)
+                           (deps ...) (search-paths ...))
        #~(#$name #$version #$output
-                 (ungexp package (or output "out")) #$deps))))
+                 (ungexp package (or output "out"))
+                 (propagated-inputs #$deps)
+                 (search-paths #$(map search-path-specification->sexp
+                                      search-paths))))))
 
   (match manifest
     (($ <manifest> (entries ...))
-     #~(manifest (version 1)
+     #~(manifest (version 2)
                  (packages #$(map entry->gexp entries))))))
 
+(define (find-package name version)
+  "Return a package from the distro matching NAME and possibly VERSION.  This
+procedure is here for backward-compatibility and will eventually vanish."
+  (define find-best-packages-by-name              ;break abstractions
+    (module-ref (resolve-interface '(gnu packages))
+                'find-best-packages-by-name))
+
+   ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; the
+   ;; former traverses the module tree only once and then allows for efficient
+   ;; access via a vhash.
+   (match (find-best-packages-by-name name version)
+     ((p _ ...) p)
+     (_
+      (match (find-best-packages-by-name name #f)
+        ((p _ ...) p)
+        (_ #f)))))
+
 (define (sexp->manifest sexp)
   "Parse SEXP as a manifest."
+  (define (infer-search-paths name version)
+    ;; Infer the search path specifications for NAME-VERSION by looking up a
+    ;; same-named package in the distro.  Useful for the old manifest formats
+    ;; that did not store search path info.
+    (let ((package (find-package name version)))
+      (if package
+          (package-native-search-paths package)
+          '())))
+
   (match sexp
     (('manifest ('version 0)
                 ('packages ((name version output path) ...)))
@@ -192,7 +231,8 @@ omitted or #f, use the first output of PACKAGE."
               (name name)
               (version version)
               (output output)
-              (item path)))
+              (item path)
+              (search-paths (infer-search-paths name version))))
            name version output path)))
 
     ;; Version 1 adds a list of propagated inputs to the
@@ -214,11 +254,30 @@ omitted or #f, use the first output of PACKAGE."
                  (version version)
                  (output output)
                  (item path)
-                 (dependencies deps))))
+                 (dependencies deps)
+                 (search-paths (infer-search-paths name version)))))
            name version output path deps)))
 
+    ;; Version 2 adds search paths and is slightly more verbose.
+    (('manifest ('version 2 minor-version ...)
+                ('packages ((name version output path
+                                  ('propagated-inputs deps)
+                                  ('search-paths search-paths)
+                                  extra-stuff ...)
+                            ...)))
+     (manifest
+      (map (lambda (name version output path deps search-paths)
+             (manifest-entry
+               (name name)
+               (version version)
+               (output output)
+               (item path)
+               (dependencies deps)
+               (search-paths (map sexp->search-path-specification
+                                  search-paths))))
+           name version output path deps search-paths)))
     (_
-     (error "unsupported manifest format" manifest))))
+     (error "unsupported manifest format" sexp))))
 
 (define (read-manifest port)
   "Return the packages listed in MANIFEST."
@@ -398,15 +457,12 @@ MANIFEST."
                (append-map info-files
                            '#$(manifest-inputs manifest)))))
 
-  ;; Don't depend on Texinfo when there's nothing to do.
-  (if (null? (manifest-entries manifest))
-      (gexp->derivation "info-dir" #~(mkdir #$output))
-      (gexp->derivation "info-dir" build
-                        #:modules '((guix build utils)))))
+  (gexp->derivation "info-dir" build
+                    #:modules '((guix build utils))))
 
 (define (ghc-package-cache-file manifest)
   "Return a derivation that builds the GHC 'package.cache' file for all the
-entries of MANIFEST."
+entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
   (define ghc                                 ;lazy reference
     (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
 
@@ -446,12 +502,11 @@ entries of MANIFEST."
           success)))
 
   ;; Don't depend on GHC when there's nothing to do.
-  (if (any (cut string-prefix? "ghc" <>)
-           (map manifest-entry-name (manifest-entries manifest)))
-      (gexp->derivation "ghc-package-cache" build
-                        #:modules '((guix build utils))
-                        #:local-build? #t)
-      (gexp->derivation "ghc-package-cache" #~(mkdir #$output))))
+  (and (any (cut string-prefix? "ghc" <>)
+            (map manifest-entry-name (manifest-entries manifest)))
+       (gexp->derivation "ghc-package-cache" build
+                         #:modules '((guix build utils))
+                         #:local-build? #t)))
 
 (define (ca-certificate-bundle manifest)
   "Return a derivation that builds a single-file bundle containing the CA
@@ -469,7 +524,8 @@ MANIFEST.  Single-file bundles are required by programs such as Git and Lynx."
                      (rnrs io ports)
                      (srfi srfi-1)
                      (srfi srfi-26)
-                     (ice-9 ftw))
+                     (ice-9 ftw)
+                     (ice-9 match))
 
         (define (pem-file? file)
           (string-suffix? ".pem" file))
@@ -495,50 +551,47 @@ MANIFEST.  Single-file bundles are required by programs such as Git and Lynx."
         (setenv "LOCPATH" (string-append #+glibc-utf8-locales "/lib/locale"))
         (setlocale LC_ALL "en_US.UTF-8")
 
-        (let ((ca-files (append-map ca-files
-                                    '#$(manifest-inputs manifest)))
-              (result   (string-append #$output "/etc/ssl/certs")))
-          (mkdir-p result)
-          (concatenate-files ca-files
-                             (string-append result
-                                            "/ca-certificates.crt")))))
-
-  ;; Don't depend on 'glibc-utf8-locales' and its dependencies when there's
-  ;; nothing to do.
-  (if (null? (manifest-entries manifest))
-      (gexp->derivation "ca-certificate-bundle" #~(mkdir #$output))
-      (gexp->derivation "ca-certificate-bundle" build
-                        #:modules '((guix build utils))
-                        #:local-build? #t)))
+        (match (append-map ca-files '#$(manifest-inputs manifest))
+          (()
+           ;; Since there are no CA files, just create an empty directory.  Do
+           ;; not create the etc/ssl/certs sub-directory, since that would
+           ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
+           ;; defined.
+           (mkdir #$output)
+           #t)
+          ((ca-files ...)
+           (let ((result (string-append #$output "/etc/ssl/certs")))
+             (mkdir-p result)
+             (concatenate-files ca-files
+                                (string-append result
+                                               "/ca-certificates.crt"))
+             #t)))))
+
+  (gexp->derivation "ca-certificate-bundle" build
+                    #:modules '((guix build utils))
+                    #:local-build? #t))
+
+(define %default-profile-hooks
+  ;; This is the list of derivation-returning procedures that are called by
+  ;; default when making a non-empty profile.
+  (list info-dir-file
+        ghc-package-cache-file
+        ca-certificate-bundle))
 
 (define* (profile-derivation manifest
                              #:key
-                             (info-dir? #t)
-                             (ghc-package-cache? #t)
-                             (ca-certificate-bundle? #t))
+                             (hooks %default-profile-hooks))
   "Return a derivation that builds a profile (aka. 'user environment') with
-the given MANIFEST.  The profile includes a top-level Info 'dir' file unless
-INFO-DIR? is #f, a GHC 'package.cache' file unless GHC-PACKAGE-CACHE? is #f
-and a single-file CA certificate bundle unless CA-CERTIFICATE-BUNDLE? is #f."
-  (mlet %store-monad ((info-dir (if info-dir?
-                                    (info-dir-file manifest)
-                                    (return #f)))
-                      (ghc-package-cache (if ghc-package-cache?
-                                             (ghc-package-cache-file manifest)
-                                             (return #f)))
-                      (ca-cert-bundle (if ca-certificate-bundle?
-                                          (ca-certificate-bundle manifest)
-                                          (return #f))))
+the given MANIFEST.  The profile includes additional derivations returned by
+the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc."
+  (mlet %store-monad ((extras (if (null? (manifest-entries manifest))
+                                  (return '())
+                                  (sequence %store-monad
+                                            (filter-map (lambda (hook)
+                                                          (hook manifest))
+                                                        hooks)))))
     (define inputs
-      (append (if info-dir
-                  (list (gexp-input info-dir))
-                  '())
-              (if ghc-package-cache
-                  (list (gexp-input ghc-package-cache))
-                  '())
-              (if ca-cert-bundle
-                  (list (gexp-input ca-cert-bundle))
-                  '())
+      (append (map gexp-input extras)
               (manifest-inputs manifest)))
 
     (define builder