profiles: Generate an 'etc/profile' file.

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.
This commit is contained in:
Ludovic Courtès 2015-05-06 17:08:00 +02:00
parent 611adb1ee5
commit d664f1b431
5 changed files with 118 additions and 7 deletions

View file

@ -14,6 +14,7 @@
((indent-tabs-mode . nil) ((indent-tabs-mode . nil)
(eval . (put 'eval-when 'scheme-indent-function 1)) (eval . (put 'eval-when 'scheme-indent-function 1))
(eval . (put 'test-assert '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-equal 'scheme-indent-function 1))
(eval . (put 'test-eq 'scheme-indent-function 1)) (eval . (put 'test-eq 'scheme-indent-function 1))
(eval . (put 'call-with-input-string 'scheme-indent-function 1)) (eval . (put 'call-with-input-string 'scheme-indent-function 1))

View file

@ -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 current generation of the user's default profile. Thus, users can add
@file{$HOME/.guix-profile/bin} to their @code{PATH} environment @file{$HOME/.guix-profile/bin} to their @code{PATH} environment
variable, and so on. 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 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 a @dfn{garbage-collector root}, which @file{$HOME/.guix-profile} points

View file

@ -18,6 +18,10 @@
(define-module (guix build profiles) (define-module (guix build profiles)
#:use-module (guix build union) #: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) #:use-module (ice-9 pretty-print)
#:export (build-profile)) #:export (build-profile))
@ -28,14 +32,71 @@ (define-module (guix build profiles)
;;; ;;;
;;; Code: ;;; 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 (define* (build-profile output inputs
#:key manifest) #:key manifest search-paths)
"Build a user profile from INPUTS in directory OUTPUT. Write MANIFEST, an "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 (union-build output inputs
#:log-port (%make-void-port "w")) #:log-port (%make-void-port "w"))
;; Store meta-data.
(call-with-output-file (string-append output "/manifest") (call-with-output-file (string-append output "/manifest")
(lambda (p) (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 ;;; profile.scm ends here

View file

@ -598,17 +598,30 @@ (define inputs
(define builder (define builder
#~(begin #~(begin
(use-modules (guix build profiles)) (use-modules (guix build profiles)
(guix search-paths))
(setvbuf (current-output-port) _IOLBF) (setvbuf (current-output-port) _IOLBF)
(setvbuf (current-error-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 (build-profile #$output '#$inputs
#:manifest '#$(manifest->gexp manifest)))) #:manifest '#$(manifest->gexp manifest)
#:search-paths search-paths)))
(gexp->derivation "profile" builder (gexp->derivation "profile" builder
#:modules '((guix build union) #:modules '((guix build profiles)
(guix build profiles)) (guix build union)
(guix build utils)
(guix search-paths)
(guix records))
#:local-build? #t))) #:local-build? #t)))
(define (profile-regexp profile) (define (profile-regexp profile)

View file

@ -29,6 +29,8 @@ (define-module (test-profiles)
#:use-module ((gnu packages guile) #:prefix packages:) #:use-module ((gnu packages guile) #:prefix packages:)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module (ice-9 regex) #:use-module (ice-9 regex)
#:use-module (ice-9 popen)
#:use-module (rnrs io ports)
#:use-module (srfi srfi-11) #:use-module (srfi srfi-11)
#:use-module (srfi srfi-64)) #:use-module (srfi srfi-64))
@ -220,6 +222,30 @@ (define glibc
(manifest-entry-search-paths entry) (manifest-entry-search-paths entry)
(package-native-search-paths (package-native-search-paths
packages:guile-2.0))))))))) 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") (test-end "profiles")