profiles.scm 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2017-2022 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-1)
  23. #:use-module (srfi srfi-11)
  24. #:use-module (srfi srfi-26)
  25. #:use-module (ice-9 ftw)
  26. #:use-module (ice-9 match)
  27. #:use-module (ice-9 pretty-print)
  28. #:re-export (symlink-relative) ;for convenience
  29. #:export (ensure-writable-directory
  30. build-profile))
  31. ;;; Commentary:
  32. ;;;
  33. ;;; Build a user profile (essentially the union of all the installed packages)
  34. ;;; with its associated meta-data.
  35. ;;;
  36. ;;; Code:
  37. (define (abstract-profile profile)
  38. "Return a procedure that replaces PROFILE in VALUE with a reference to the
  39. 'GUIX_PROFILE' environment variable. This allows users to specify what the
  40. user-friendly name of the profile is, for instance ~/.guix-profile rather than
  41. /gnu/store/...-profile."
  42. (let ((replacement (string-append "${GUIX_PROFILE:-" profile "}"))
  43. (crop (cute string-drop <> (string-length profile))))
  44. (match-lambda
  45. ((search-path . value)
  46. (match (search-path-specification-separator search-path)
  47. (#f
  48. (cons search-path
  49. (string-append replacement (crop value))))
  50. ((? string? separator)
  51. (let ((items (string-tokenize* value separator)))
  52. (cons search-path
  53. (string-join (map (lambda (str)
  54. (string-append replacement (crop str)))
  55. items)
  56. separator)))))))))
  57. (define (write-environment-variable-definition port)
  58. "Write the given environment variable definition to PORT."
  59. (match-lambda
  60. ((search-path . value)
  61. (display (search-path-definition search-path value #:kind 'prefix)
  62. port)
  63. (newline port))))
  64. (define (build-etc/profile output search-paths)
  65. "Build the 'OUTPUT/etc/profile' shell file containing environment variable
  66. definitions for all the SEARCH-PATHS."
  67. (define file
  68. (string-append output "/etc/profile"))
  69. (mkdir-p (dirname file))
  70. (when (file-exists? file)
  71. (delete-file file))
  72. (call-with-output-file file
  73. (lambda (port)
  74. ;; The use of $GUIX_PROFILE described below is not great. Another
  75. ;; option would have been to use "$1" and have users run:
  76. ;;
  77. ;; source ~/.guix-profile/etc/profile ~/.guix-profile
  78. ;;
  79. ;; However, when 'source' is used with no arguments, $1 refers to the
  80. ;; first positional parameter of the calling script, so we cannot rely
  81. ;; on it.
  82. (display "\
  83. # Source this file to define all the relevant environment variables in Bash
  84. # for this profile. You may want to define the 'GUIX_PROFILE' environment
  85. # variable to point to the \"visible\" name of the profile, like this:
  86. #
  87. # GUIX_PROFILE=/path/to/profile ; \\
  88. # source /path/to/profile/etc/profile
  89. #
  90. # When GUIX_PROFILE is undefined, the various environment variables refer
  91. # to this specific profile generation.
  92. \n" port)
  93. (let ((variables (evaluate-search-paths search-paths
  94. (list output))))
  95. (for-each (write-environment-variable-definition port)
  96. (map (abstract-profile output) variables))))))
  97. (define* (ensure-writable-directory directory
  98. #:key (symlink symlink))
  99. "Ensure DIRECTORY exists and is writable. If DIRECTORY is currently a
  100. symlink (to a read-only directory in the store), then delete the symlink and
  101. instead make DIRECTORY a \"real\" directory containing symlinks."
  102. (define (absolute? file)
  103. (string-prefix? "/" file))
  104. (define (unsymlink link)
  105. (let* ((target (match (readlink link)
  106. ((? absolute? target)
  107. target)
  108. ((? string? relative)
  109. (string-append (dirname link) "/" relative))))
  110. ;; TARGET might itself be a symlink, so append "/" to make sure
  111. ;; 'scandir' enters it.
  112. (files (scandir (string-append target "/")
  113. (negate (cut member <> '("." ".."))))))
  114. (delete-file link)
  115. (mkdir link)
  116. (for-each (lambda (file)
  117. (symlink (string-append target "/" file)
  118. (string-append link "/" file)))
  119. files)))
  120. (catch 'system-error
  121. (lambda ()
  122. (mkdir directory))
  123. (lambda args
  124. (let ((errno (system-error-errno args)))
  125. (if (= errno EEXIST)
  126. (let ((stat (lstat directory)))
  127. (case (stat:type stat)
  128. ((symlink)
  129. ;; "Unsymlink" DIRECTORY so that it is writable.
  130. (unsymlink directory))
  131. ((directory)
  132. #t)
  133. (else
  134. (error "cannot mkdir because a same-named file exists"
  135. directory))))
  136. (apply throw args))))))
  137. (define (manifest-sexp->inputs+search-paths manifest)
  138. "Parse MANIFEST, an sexp as produced by 'manifest->gexp', and return two
  139. values: the list of store items of its manifest entries, and the list of
  140. search path specifications."
  141. (define-syntax let-fields
  142. (syntax-rules ()
  143. ;; Bind the fields NAME of LST to same-named variables in the lexical
  144. ;; scope of BODY.
  145. ((_ lst (name rest ...) body ...)
  146. (let ((name (match (assq 'name lst)
  147. ((_ value) value)
  148. (#f '()))))
  149. (let-fields lst (rest ...) body ...)))
  150. ((_ lst () body ...)
  151. (begin body ...))))
  152. (match manifest ;this must match 'manifest->gexp'
  153. (('manifest ('version (or 3 4))
  154. ('packages (entries ...)))
  155. (let loop ((entries entries)
  156. (inputs '())
  157. (search-paths '()))
  158. (match entries
  159. (((name version output item fields ...) . rest)
  160. (let ((paths search-paths))
  161. (let-fields fields (propagated-inputs search-paths)
  162. (loop (append rest propagated-inputs) ;breadth-first traversal
  163. (cons item inputs)
  164. (append search-paths paths)))))
  165. ((('repeated name version item) . rest)
  166. (loop rest inputs search-paths))
  167. (()
  168. (values (reverse inputs)
  169. (delete-duplicates
  170. (cons* $PATH
  171. $GUIX_EXTENSIONS_PATH
  172. (map sexp->search-path-specification
  173. (reverse search-paths)))))))))))
  174. (define* (build-profile output manifest
  175. #:key (extra-inputs '()) (symlink symlink))
  176. "Build a user profile from MANIFEST, an sexp, and EXTRA-INPUTS, a list of
  177. store items, in directory OUTPUT, using SYMLINK to create symlinks. Create
  178. OUTPUT/etc/profile with Bash definitions for all the variables listed in the
  179. search paths of MANIFEST's entries."
  180. (define manifest-file
  181. (string-append output "/manifest"))
  182. (let-values (((inputs search-paths)
  183. (manifest-sexp->inputs+search-paths manifest)))
  184. ;; Make the symlinks.
  185. (union-build output (append extra-inputs inputs)
  186. #:symlink symlink
  187. #:log-port (%make-void-port "w"))
  188. ;; If one of the INPUTS provides a '/manifest' file, delete it. That can
  189. ;; happen if MANIFEST contains something such as a Guix instance, which is
  190. ;; ultimately built as a profile.
  191. (when (file-exists? manifest-file)
  192. (delete-file manifest-file))
  193. ;; Store meta-data.
  194. (call-with-output-file manifest-file
  195. (lambda (p)
  196. (display "\
  197. ;; This file was automatically generated and is for internal use only.
  198. ;; It cannot be passed to the '--manifest' option.
  199. ;; Run 'guix package --export-manifest' if you want to export a file
  200. ;; suitable for '--manifest'.\n\n"
  201. p)
  202. (pretty-print manifest p)))
  203. ;; Make sure we can write to 'OUTPUT/etc'. 'union-build' above could have
  204. ;; made 'etc' a symlink to a read-only sub-directory in the store so we
  205. ;; need to work around that.
  206. (ensure-writable-directory (string-append output "/etc")
  207. #:symlink symlink)
  208. ;; Write 'OUTPUT/etc/profile'.
  209. (build-etc/profile output search-paths)))
  210. ;;; Local Variables:
  211. ;;; eval: (put 'let-fields 'scheme-indent-function 2)
  212. ;;; End:
  213. ;;; profile.scm ends here