From dedb17ad010ee9ef67f3f4f3997dd17f226c8090 Mon Sep 17 00:00:00 2001
From: Ludovic Courtès <ludo@gnu.org>
Date: Sat, 2 May 2015 23:55:24 +0200
Subject: profiles: Store search paths in manifests.

Discussed in <http://bugs.gnu.org/20255>.

* guix/packages.scm (sexp->search-path-specification): New variable.
* guix/profiles.scm (<manifest-entry>)[search-paths]: New field.
  (package->manifest-entry): Initialize it.
  (manifest->gexp): Match it.  Wrap #$deps in (propagated-inputs ...).
  Emit (search-paths ...).  Increment version.
  (find-package): New procedure.
  (sexp->manifest)[infer-search-paths]: New procedure.
  Use it to initialize the 'search-paths' field for versions 0 and 1.
  Add case for version 2.
* guix/scripts/package.scm (search-path-environment-variables)[manifest-entry->package]:
  Remove.
  Use 'manifest-entry-search-paths' instead of 'manifest-entry->package'
  plus 'package-native-search-paths'.
* tests/profiles.scm ("profile-manifest, search-paths"): New test.
---
 tests/profiles.scm | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

(limited to 'tests/profiles.scm')

diff --git a/tests/profiles.scm b/tests/profiles.scm
index 54fbaea864..890f09a751 100644
--- a/tests/profiles.scm
+++ b/tests/profiles.scm
@@ -26,6 +26,7 @@
   #:use-module (guix derivations)
   #:use-module (gnu packages bootstrap)
   #:use-module ((gnu packages base) #:prefix packages:)
+  #:use-module ((gnu packages guile) #:prefix packages:)
   #:use-module (ice-9 match)
   #:use-module (ice-9 regex)
   #:use-module (srfi srfi-11)
@@ -198,6 +199,27 @@
                                        #:hooks '())))
     (return (derivation-inputs drv))))
 
+(test-assertm "profile-manifest, search-paths"
+  (mlet* %store-monad
+      ((guile ->   (package
+                     (inherit %bootstrap-guile)
+                     (native-search-paths
+                      (package-native-search-paths packages:guile-2.0))))
+       (entry ->   (package->manifest-entry guile))
+       (drv        (profile-derivation (manifest (list entry))
+                                       #:hooks '()))
+       (profile -> (derivation->output-path drv)))
+    (mbegin %store-monad
+      (built-derivations (list drv))
+
+      ;; Read the manifest back and make sure search paths are preserved.
+      (let ((manifest (profile-manifest profile)))
+        (match (manifest-entries manifest)
+          ((result)
+           (return (equal? (manifest-entry-search-paths result)
+                           (manifest-entry-search-paths entry)
+                           (package-native-search-paths
+                            packages:guile-2.0)))))))))
 (test-end "profiles")
 
 
-- 
cgit 1.4.1


From d664f1b431d2a64ff58ddc4ccce40e187947b960 Mon Sep 17 00:00:00 2001
From: Ludovic Courtès <ludo@gnu.org>
Date: Wed, 6 May 2015 17:08:00 +0200
Subject: profiles: Generate an 'etc/profile' file.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Suggested by 宋文武 <iyzsong@gmail.com>
in <http://bugs.gnu.org/20255>.

* guix/build/profiles.scm (abstract-profile,
  write-environment-variable-definition): New procedures.
  (build-profile): Add #:search-paths parameter.  Create
  OUTPUT/etc/profile.
* guix/profiles.scm (profile-derivation)[builder]: Add 'search-paths'
  variable and pass it to 'build-profile'.  Adjust #:modules argument.
* tests/profiles.scm ("etc/profile"): New test.
* doc/guix.texi (Invoking guix package): Mention etc/profile.
---
 .dir-locals.el          |  1 +
 doc/guix.texi           | 10 ++++++++
 guix/build/profiles.scm | 67 ++++++++++++++++++++++++++++++++++++++++++++++---
 guix/profiles.scm       | 21 +++++++++++++---
 tests/profiles.scm      | 26 +++++++++++++++++++
 5 files changed, 118 insertions(+), 7 deletions(-)

(limited to 'tests/profiles.scm')

diff --git a/.dir-locals.el b/.dir-locals.el
index 7aef853625..eb3da94da4 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -14,6 +14,7 @@
   ((indent-tabs-mode . nil)
    (eval . (put 'eval-when 'scheme-indent-function 1))
    (eval . (put 'test-assert 'scheme-indent-function 1))
+   (eval . (put 'test-assertm 'scheme-indent-function 1))
    (eval . (put 'test-equal 'scheme-indent-function 1))
    (eval . (put 'test-eq 'scheme-indent-function 1))
    (eval . (put 'call-with-input-string 'scheme-indent-function 1))
diff --git a/doc/guix.texi b/doc/guix.texi
index 8241cb07bf..1b1690a8e3 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -950,6 +950,16 @@ created in @file{$HOME/.guix-profile}.  This symlink always points to the
 current generation of the user's default profile.  Thus, users can add
 @file{$HOME/.guix-profile/bin} to their @code{PATH} environment
 variable, and so on.
+@cindex search paths
+If you are not using the Guix System Distribution, consider adding the
+following lines to your @file{~/.bash_profile} (@pxref{Bash Startup
+Files,,, bash, The GNU Bash Reference Manual}) so that newly-spawned
+shells get all the right environment variable definitions:
+
+@example
+GUIX_PROFILE="$HOME/.guix-profile" \
+source "$HOME/.guix-profile/etc/profile"
+@end example
 
 In a multi-user setup, user profiles are stored in a place registered as
 a @dfn{garbage-collector root}, which @file{$HOME/.guix-profile} points
diff --git a/guix/build/profiles.scm b/guix/build/profiles.scm
index 1c5b54e40b..eda54cb37a 100644
--- a/guix/build/profiles.scm
+++ b/guix/build/profiles.scm
@@ -18,6 +18,10 @@
 
 (define-module (guix build profiles)
   #:use-module (guix build union)
+  #:use-module (guix build utils)
+  #:use-module (guix search-paths)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 match)
   #:use-module (ice-9 pretty-print)
   #:export (build-profile))
 
@@ -28,14 +32,71 @@
 ;;;
 ;;; Code:
 
+(define (abstract-profile profile)
+  "Return a procedure that replaces PROFILE in VALUE with a reference to the
+'GUIX_PROFILE' environment variable.  This allows users to specify what the
+user-friendly name of the profile is, for instance ~/.guix-profile rather than
+/gnu/store/...-profile."
+  (let ((replacement (string-append "${GUIX_PROFILE:-" profile "}")))
+    (match-lambda
+      ((search-path . value)
+       (let* ((separator (search-path-specification-separator search-path))
+              (items     (string-tokenize* value separator))
+              (crop      (cute string-drop <> (string-length profile))))
+         (cons search-path
+               (string-join (map (lambda (str)
+                                   (string-append replacement (crop str)))
+                                 items)
+                            separator)))))))
+
+(define (write-environment-variable-definition port)
+  "Write the given environment variable definition to PORT."
+  (match-lambda
+    ((search-path . value)
+     (display (search-path-definition search-path value #:kind 'prefix)
+              port)
+     (newline port))))
+
 (define* (build-profile output inputs
-                        #:key manifest)
+                        #:key manifest search-paths)
   "Build a user profile from INPUTS in directory OUTPUT.  Write MANIFEST, an
-sexp, to OUTPUT/manifest."
+sexp, to OUTPUT/manifest.  Create OUTPUT/etc/profile with Bash definitions for
+all the variables listed in SEARCH-PATHS."
+  ;; Make the symlinks.
   (union-build output inputs
                #:log-port (%make-void-port "w"))
+
+  ;; Store meta-data.
   (call-with-output-file (string-append output "/manifest")
     (lambda (p)
-      (pretty-print manifest p))))
+      (pretty-print manifest p)))
+
+  ;; Add a ready-to-use Bash profile.
+  (mkdir-p (string-append output "/etc"))
+  (call-with-output-file (string-append output "/etc/profile")
+    (lambda (port)
+      ;; The use of $GUIX_PROFILE described below is not great.  Another
+      ;; option would have been to use "$1" and have users run:
+      ;;
+      ;;   source ~/.guix-profile/etc/profile ~/.guix-profile
+      ;;
+      ;; However, when 'source' is used with no arguments, $1 refers to the
+      ;; first positional parameter of the calling scripts, so we can rely on
+      ;; it.
+      (display "\
+# Source this file to define all the relevant environment variables in Bash
+# for this profile.  You may want to define the 'GUIX_PROFILE' environment
+# variable to point to the \"visible\" name of the profile, like this:
+#
+#  GUIX_PROFILE=/path/to/profile
+#  source /path/to/profile/etc/profile
+#
+# When GUIX_PROFILE is undefined, the various environment variables refer
+# to this specific profile generation.
+\n" port)
+      (let ((variables (evaluate-search-paths (cons $PATH search-paths)
+                                              (list output))))
+        (for-each (write-environment-variable-definition port)
+                  (map (abstract-profile output) variables))))))
 
 ;;; profile.scm ends here
diff --git a/guix/profiles.scm b/guix/profiles.scm
index afc22e118d..11d9bf0cd9 100644
--- a/guix/profiles.scm
+++ b/guix/profiles.scm
@@ -598,17 +598,30 @@ the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc."
 
     (define builder
       #~(begin
-          (use-modules (guix build profiles))
+          (use-modules (guix build profiles)
+                       (guix search-paths))
 
           (setvbuf (current-output-port) _IOLBF)
           (setvbuf (current-error-port) _IOLBF)
 
+          (define search-paths
+            ;; Search paths of MANIFEST's packages, converted back to their
+            ;; record form.
+            (map sexp->search-path-specification
+                 '#$(map search-path-specification->sexp
+                         (append-map manifest-entry-search-paths
+                                     (manifest-entries manifest)))))
+
           (build-profile #$output '#$inputs
-                         #:manifest '#$(manifest->gexp manifest))))
+                         #:manifest '#$(manifest->gexp manifest)
+                         #:search-paths search-paths)))
 
     (gexp->derivation "profile" builder
-                      #:modules '((guix build union)
-                                  (guix build profiles))
+                      #:modules '((guix build profiles)
+                                  (guix build union)
+                                  (guix build utils)
+                                  (guix search-paths)
+                                  (guix records))
                       #:local-build? #t)))
 
 (define (profile-regexp profile)
diff --git a/tests/profiles.scm b/tests/profiles.scm
index 890f09a751..a39717191d 100644
--- a/tests/profiles.scm
+++ b/tests/profiles.scm
@@ -29,6 +29,8 @@
   #:use-module ((gnu packages guile) #:prefix packages:)
   #:use-module (ice-9 match)
   #:use-module (ice-9 regex)
+  #:use-module (ice-9 popen)
+  #:use-module (rnrs io ports)
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-64))
 
@@ -220,6 +222,30 @@
                            (manifest-entry-search-paths entry)
                            (package-native-search-paths
                             packages:guile-2.0)))))))))
+
+(test-assertm "etc/profile"
+  ;; Make sure we get an 'etc/profile' file that at least defines $PATH.
+  (mlet* %store-monad
+      ((guile ->   (package
+                     (inherit %bootstrap-guile)
+                     (native-search-paths
+                      (package-native-search-paths packages:guile-2.0))))
+       (entry ->   (package->manifest-entry guile))
+       (drv        (profile-derivation (manifest (list entry))
+                                       #:hooks '()))
+       (profile -> (derivation->output-path drv)))
+    (mbegin %store-monad
+      (built-derivations (list drv))
+      (let* ((pipe (open-input-pipe
+                    (string-append "source "
+                                   profile "/etc/profile; "
+                                   "unset GUIX_PROFILE; set")))
+             (env  (get-string-all pipe)))
+        (return
+         (and (zero? (close-pipe pipe))
+              (string-contains env
+                               (string-append "PATH=" profile "/bin"))))))))
+
 (test-end "profiles")
 
 
-- 
cgit 1.4.1


From 9e006fb3de0bd1af2434b32c3be23101e00b1b10 Mon Sep 17 00:00:00 2001
From: Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
Date: Wed, 6 May 2015 20:13:31 +0200
Subject: tests: Fix etc/profile test.

* tests/profiles.scm ("etc/profile"): Unset GUIX_PROFILE before sourcing
  etc/profile.  Use '.' instead of 'source' for sourcing.  Call 'echo $PATH'
  instead of using the output of 'set' to determine whether PATH is set.
---
 tests/profiles.scm | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

(limited to 'tests/profiles.scm')

diff --git a/tests/profiles.scm b/tests/profiles.scm
index a39717191d..de1411dca2 100644
--- a/tests/profiles.scm
+++ b/tests/profiles.scm
@@ -237,14 +237,16 @@
     (mbegin %store-monad
       (built-derivations (list drv))
       (let* ((pipe (open-input-pipe
-                    (string-append "source "
-                                   profile "/etc/profile; "
-                                   "unset GUIX_PROFILE; set")))
-             (env  (get-string-all pipe)))
+                    (string-append "unset GUIX_PROFILE; "
+                                   ;; 'source' is a Bashism; use '.' (dot).
+                                   ". " profile "/etc/profile; "
+                                   ;; Don't try to parse set(1) output because
+                                   ;; it differs among shells; just use echo.
+                                   "echo $PATH")))
+             (path (get-string-all pipe)))
         (return
          (and (zero? (close-pipe pipe))
-              (string-contains env
-                               (string-append "PATH=" profile "/bin"))))))))
+              (string-contains path (string-append profile "/bin"))))))))
 
 (test-end "profiles")
 
-- 
cgit 1.4.1


From a0dac7a01f766e75dc73200a889f31c3920a2d98 Mon Sep 17 00:00:00 2001
From: Ludovic Courtès <ludo@gnu.org>
Date: Fri, 8 May 2015 15:39:45 +0200
Subject: profiles: Ensure the profile's etc/ directory is writable.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Reported by 宋文武 <iyzsong@gmail.com>.

* guix/build/profiles.scm (build-etc/profile,
  ensure-writable-directory): New procedures.
  (build-profile): Use them.
* tests/profiles.scm ("etc/profile when etc/ already exists"): New test.
---
 guix/build/profiles.scm | 74 +++++++++++++++++++++++++++++++++++++++----------
 tests/profiles.scm      | 29 +++++++++++++++++++
 2 files changed, 88 insertions(+), 15 deletions(-)

(limited to 'tests/profiles.scm')

diff --git a/guix/build/profiles.scm b/guix/build/profiles.scm
index eda54cb37a..525d59b979 100644
--- a/guix/build/profiles.scm
+++ b/guix/build/profiles.scm
@@ -21,6 +21,7 @@
   #:use-module (guix build utils)
   #:use-module (guix search-paths)
   #:use-module (srfi srfi-26)
+  #:use-module (ice-9 ftw)
   #:use-module (ice-9 match)
   #:use-module (ice-9 pretty-print)
   #:export (build-profile))
@@ -57,21 +58,9 @@ user-friendly name of the profile is, for instance ~/.guix-profile rather than
               port)
      (newline port))))
 
-(define* (build-profile output inputs
-                        #:key manifest search-paths)
-  "Build a user profile from INPUTS in directory OUTPUT.  Write MANIFEST, an
-sexp, to OUTPUT/manifest.  Create OUTPUT/etc/profile with Bash definitions for
-all the variables listed in SEARCH-PATHS."
-  ;; Make the symlinks.
-  (union-build output inputs
-               #:log-port (%make-void-port "w"))
-
-  ;; Store meta-data.
-  (call-with-output-file (string-append output "/manifest")
-    (lambda (p)
-      (pretty-print manifest p)))
-
-  ;; Add a ready-to-use Bash profile.
+(define (build-etc/profile output search-paths)
+  "Build the 'OUTPUT/etc/profile' shell file containing environment variable
+definitions for all the SEARCH-PATHS."
   (mkdir-p (string-append output "/etc"))
   (call-with-output-file (string-append output "/etc/profile")
     (lambda (port)
@@ -99,4 +88,59 @@ all the variables listed in SEARCH-PATHS."
         (for-each (write-environment-variable-definition port)
                   (map (abstract-profile output) variables))))))
 
+(define (ensure-writable-directory directory)
+  "Ensure DIRECTORY exists and is writable.  If DIRECTORY is currently a
+symlink (to a read-only directory in the store), then delete the symlink and
+instead make DIRECTORY a \"real\" directory containing symlinks."
+  (define (unsymlink link)
+    (let* ((target (readlink link))
+           (files  (scandir target
+                            (negate (cut member <> '("." ".."))))))
+      (delete-file link)
+      (mkdir link)
+      (for-each (lambda (file)
+                  (symlink (string-append target "/" file)
+                           (string-append link "/" file)))
+                files)))
+
+  (catch 'system-error
+    (lambda ()
+      (mkdir directory))
+    (lambda args
+      (let ((errno (system-error-errno args)))
+        (if (= errno EEXIST)
+            (let ((stat (lstat directory)))
+              (case (stat:type stat)
+                ((symlink)
+                 ;; "Unsymlink" DIRECTORY so that it is writable.
+                 (unsymlink directory))
+                ((directory)
+                 #t)
+                (else
+                 (error "cannot mkdir because a same-named file exists"
+                        directory))))
+            (apply throw args))))))
+
+(define* (build-profile output inputs
+                        #:key manifest search-paths)
+  "Build a user profile from INPUTS in directory OUTPUT.  Write MANIFEST, an
+sexp, to OUTPUT/manifest.  Create OUTPUT/etc/profile with Bash definitions for
+-all the variables listed in SEARCH-PATHS."
+  ;; Make the symlinks.
+  (union-build output inputs
+               #:log-port (%make-void-port "w"))
+
+  ;; Store meta-data.
+  (call-with-output-file (string-append output "/manifest")
+    (lambda (p)
+      (pretty-print manifest p)))
+
+  ;; Make sure we can write to 'OUTPUT/etc'.  'union-build' above could have
+  ;; made 'etc' a symlink to a read-only sub-directory in the store so we need
+  ;; to work around that.
+  (ensure-writable-directory (string-append output "/etc"))
+
+  ;; Write 'OUTPUT/etc/profile'.
+  (build-etc/profile output search-paths))
+
 ;;; profile.scm ends here
diff --git a/tests/profiles.scm b/tests/profiles.scm
index de1411dca2..ac7f28bf53 100644
--- a/tests/profiles.scm
+++ b/tests/profiles.scm
@@ -24,6 +24,7 @@
   #:use-module (guix monads)
   #:use-module (guix packages)
   #:use-module (guix derivations)
+  #:use-module (guix build-system trivial)
   #:use-module (gnu packages bootstrap)
   #:use-module ((gnu packages base) #:prefix packages:)
   #:use-module ((gnu packages guile) #:prefix packages:)
@@ -248,6 +249,34 @@
          (and (zero? (close-pipe pipe))
               (string-contains path (string-append profile "/bin"))))))))
 
+(test-assertm "etc/profile when etc/ already exists"
+  ;; Here 'union-build' makes the profile's etc/ a symlink to the package's
+  ;; etc/ directory, which makes it read-only.  Make sure the profile build
+  ;; handles that.
+  (mlet* %store-monad
+      ((thing ->   (dummy-package "dummy"
+                     (build-system trivial-build-system)
+                     (arguments
+                      `(#:guile ,%bootstrap-guile
+                        #:builder
+                        (let ((out (assoc-ref %outputs "out")))
+                          (mkdir out)
+                          (mkdir (string-append out "/etc"))
+                          (call-with-output-file (string-append out "/etc/foo")
+                            (lambda (port)
+                              (display "foo!" port))))))))
+       (entry ->   (package->manifest-entry thing))
+       (drv        (profile-derivation (manifest (list entry))
+                                       #:hooks '()))
+       (profile -> (derivation->output-path drv)))
+    (mbegin %store-monad
+      (built-derivations (list drv))
+      (return (and (file-exists? (string-append profile "/etc/profile"))
+                   (string=? (call-with-input-file
+                                 (string-append profile "/etc/foo")
+                               get-string-all)
+                             "foo!"))))))
+
 (test-end "profiles")
 
 
-- 
cgit 1.4.1


From 113c17a0c969e600023698ae3a34994a796d0046 Mon Sep 17 00:00:00 2001
From: Ludovic Courtès <ludo@gnu.org>
Date: Mon, 18 May 2015 09:47:29 +0200
Subject: profiles: Gracefully deal with packages containing an etc/ symlink.

This fixes a bug whereby 'guix package -i gcc-toolchain' would fail in
'build-profile'.  This is because in 'gcc-toolchain', etc/ is a symlink,
and so the 'scandir' call in 'unsymlink' would return #f instead of
returning a list.

Reported by Andreas Enge <andreas.enge@inria.fr>.

* guix/build/profiles.scm (ensure-writable-directory)[unsymlink]: Append
  "/" to TARGET before calling 'scandir'.
* tests/profiles.scm ("etc/profile when etc/ is a symlink"): New test.
---
 guix/build/profiles.scm |  4 +++-
 tests/profiles.scm      | 28 ++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

(limited to 'tests/profiles.scm')

diff --git a/guix/build/profiles.scm b/guix/build/profiles.scm
index 525d59b979..2becc6b9af 100644
--- a/guix/build/profiles.scm
+++ b/guix/build/profiles.scm
@@ -94,7 +94,9 @@ symlink (to a read-only directory in the store), then delete the symlink and
 instead make DIRECTORY a \"real\" directory containing symlinks."
   (define (unsymlink link)
     (let* ((target (readlink link))
-           (files  (scandir target
+           ;; TARGET might itself be a symlink, so append "/" to make sure
+           ;; 'scandir' enters it.
+           (files  (scandir (string-append target "/")
                             (negate (cut member <> '("." ".."))))))
       (delete-file link)
       (mkdir link)
diff --git a/tests/profiles.scm b/tests/profiles.scm
index ac7f28bf53..cc9a822cee 100644
--- a/tests/profiles.scm
+++ b/tests/profiles.scm
@@ -277,6 +277,34 @@
                                get-string-all)
                              "foo!"))))))
 
+(test-assertm "etc/profile when etc/ is a symlink"
+  ;; When etc/ is a symlink, the unsymlink code in 0.8.2 would fail
+  ;; gracelessly because 'scandir' would return #f.
+  (mlet* %store-monad
+      ((thing ->   (dummy-package "dummy"
+                     (build-system trivial-build-system)
+                     (arguments
+                      `(#:guile ,%bootstrap-guile
+                        #:builder
+                        (let ((out (assoc-ref %outputs "out")))
+                          (mkdir out)
+                          (mkdir (string-append out "/foo"))
+                          (symlink "foo" (string-append out "/etc"))
+                          (call-with-output-file (string-append out "/etc/bar")
+                            (lambda (port)
+                              (display "foo!" port))))))))
+       (entry ->   (package->manifest-entry thing))
+       (drv        (profile-derivation (manifest (list entry))
+                                       #:hooks '()))
+       (profile -> (derivation->output-path drv)))
+    (mbegin %store-monad
+      (built-derivations (list drv))
+      (return (and (file-exists? (string-append profile "/etc/profile"))
+                   (string=? (call-with-input-file
+                                 (string-append profile "/etc/bar")
+                               get-string-all)
+                             "foo!"))))))
+
 (test-end "profiles")
 
 
-- 
cgit 1.4.1