profiles.scm 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix build profiles)
  19. #:use-module (guix build union)
  20. #:use-module (guix build utils)
  21. #:use-module (guix search-paths)
  22. #:use-module (srfi srfi-26)
  23. #:use-module (ice-9 ftw)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 pretty-print)
  26. #:re-export (symlink-relative) ;for convenience
  27. #:export (ensure-writable-directory
  28. build-profile))
  29. ;;; Commentary:
  30. ;;;
  31. ;;; Build a user profile (essentially the union of all the installed packages)
  32. ;;; with its associated meta-data.
  33. ;;;
  34. ;;; Code:
  35. (define (abstract-profile profile)
  36. "Return a procedure that replaces PROFILE in VALUE with a reference to the
  37. 'GUIX_PROFILE' environment variable. This allows users to specify what the
  38. user-friendly name of the profile is, for instance ~/.guix-profile rather than
  39. /gnu/store/...-profile."
  40. (let ((replacement (string-append "${GUIX_PROFILE:-" profile "}"))
  41. (crop (cute string-drop <> (string-length profile))))
  42. (match-lambda
  43. ((search-path . value)
  44. (match (search-path-specification-separator search-path)
  45. (#f
  46. (cons search-path
  47. (string-append replacement (crop value))))
  48. ((? string? separator)
  49. (let ((items (string-tokenize* value separator)))
  50. (cons search-path
  51. (string-join (map (lambda (str)
  52. (string-append replacement (crop str)))
  53. items)
  54. separator)))))))))
  55. (define (write-environment-variable-definition port)
  56. "Write the given environment variable definition to PORT."
  57. (match-lambda
  58. ((search-path . value)
  59. (display (search-path-definition search-path value #:kind 'prefix)
  60. port)
  61. (newline port))))
  62. (define (build-etc/profile output search-paths)
  63. "Build the 'OUTPUT/etc/profile' shell file containing environment variable
  64. definitions for all the SEARCH-PATHS."
  65. (define file
  66. (string-append output "/etc/profile"))
  67. (mkdir-p (dirname file))
  68. (when (file-exists? file)
  69. (delete-file file))
  70. (call-with-output-file file
  71. (lambda (port)
  72. ;; The use of $GUIX_PROFILE described below is not great. Another
  73. ;; option would have been to use "$1" and have users run:
  74. ;;
  75. ;; source ~/.guix-profile/etc/profile ~/.guix-profile
  76. ;;
  77. ;; However, when 'source' is used with no arguments, $1 refers to the
  78. ;; first positional parameter of the calling script, so we cannot rely
  79. ;; on it.
  80. (display "\
  81. # Source this file to define all the relevant environment variables in Bash
  82. # for this profile. You may want to define the 'GUIX_PROFILE' environment
  83. # variable to point to the \"visible\" name of the profile, like this:
  84. #
  85. # GUIX_PROFILE=/path/to/profile ; \\
  86. # source /path/to/profile/etc/profile
  87. #
  88. # When GUIX_PROFILE is undefined, the various environment variables refer
  89. # to this specific profile generation.
  90. \n" port)
  91. (let ((variables (evaluate-search-paths search-paths
  92. (list output))))
  93. (for-each (write-environment-variable-definition port)
  94. (map (abstract-profile output) variables))))))
  95. (define* (ensure-writable-directory directory
  96. #:key (symlink symlink))
  97. "Ensure DIRECTORY exists and is writable. If DIRECTORY is currently a
  98. symlink (to a read-only directory in the store), then delete the symlink and
  99. instead make DIRECTORY a \"real\" directory containing symlinks."
  100. (define (absolute? file)
  101. (string-prefix? "/" file))
  102. (define (unsymlink link)
  103. (let* ((target (match (readlink link)
  104. ((? absolute? target)
  105. target)
  106. ((? string? relative)
  107. (string-append (dirname link) "/" relative))))
  108. ;; TARGET might itself be a symlink, so append "/" to make sure
  109. ;; 'scandir' enters it.
  110. (files (scandir (string-append target "/")
  111. (negate (cut member <> '("." ".."))))))
  112. (delete-file link)
  113. (mkdir link)
  114. (for-each (lambda (file)
  115. (symlink (string-append target "/" file)
  116. (string-append link "/" file)))
  117. files)))
  118. (catch 'system-error
  119. (lambda ()
  120. (mkdir directory))
  121. (lambda args
  122. (let ((errno (system-error-errno args)))
  123. (if (= errno EEXIST)
  124. (let ((stat (lstat directory)))
  125. (case (stat:type stat)
  126. ((symlink)
  127. ;; "Unsymlink" DIRECTORY so that it is writable.
  128. (unsymlink directory))
  129. ((directory)
  130. #t)
  131. (else
  132. (error "cannot mkdir because a same-named file exists"
  133. directory))))
  134. (apply throw args))))))
  135. (define* (build-profile output inputs
  136. #:key manifest search-paths
  137. (symlink symlink))
  138. "Build a user profile from INPUTS in directory OUTPUT, using SYMLINK to
  139. create symlinks. Write MANIFEST, an sexp, to OUTPUT/manifest. Create
  140. OUTPUT/etc/profile with Bash definitions for -all the variables listed in
  141. SEARCH-PATHS."
  142. (define manifest-file
  143. (string-append output "/manifest"))
  144. ;; Make the symlinks.
  145. (union-build output inputs
  146. #:symlink symlink
  147. #:log-port (%make-void-port "w"))
  148. ;; If one of the INPUTS provides a '/manifest' file, delete it. That can
  149. ;; happen if MANIFEST contains something such as a Guix instance, which is
  150. ;; ultimately built as a profile.
  151. (when (file-exists? manifest-file)
  152. (delete-file manifest-file))
  153. ;; Store meta-data.
  154. (call-with-output-file manifest-file
  155. (lambda (p)
  156. (pretty-print manifest p)))
  157. ;; Make sure we can write to 'OUTPUT/etc'. 'union-build' above could have
  158. ;; made 'etc' a symlink to a read-only sub-directory in the store so we need
  159. ;; to work around that.
  160. (ensure-writable-directory (string-append output "/etc")
  161. #:symlink symlink)
  162. ;; Write 'OUTPUT/etc/profile'.
  163. (build-etc/profile output search-paths))
  164. ;;; profile.scm ends here