profiles.scm 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2017, 2018, 2019, 2020, 2021 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. (match manifest ;this must match 'manifest->gexp'
  142. (('manifest ('version 3)
  143. ('packages (entries ...)))
  144. (let loop ((entries entries)
  145. (inputs '())
  146. (search-paths '()))
  147. (match entries
  148. (((name version output item
  149. ('propagated-inputs deps)
  150. ('search-paths paths) _ ...) . rest)
  151. (loop (append rest deps) ;breadth-first traversal
  152. (cons item inputs)
  153. (append paths search-paths)))
  154. (()
  155. (values (reverse inputs)
  156. (delete-duplicates
  157. (cons $PATH
  158. (map sexp->search-path-specification
  159. (reverse search-paths)))))))))))
  160. (define* (build-profile output manifest
  161. #:key (extra-inputs '()) (symlink symlink))
  162. "Build a user profile from MANIFEST, an sexp, and EXTRA-INPUTS, a list of
  163. store items, in directory OUTPUT, using SYMLINK to create symlinks. Create
  164. OUTPUT/etc/profile with Bash definitions for all the variables listed in the
  165. search paths of MANIFEST's entries."
  166. (define manifest-file
  167. (string-append output "/manifest"))
  168. (let-values (((inputs search-paths)
  169. (manifest-sexp->inputs+search-paths manifest)))
  170. ;; Make the symlinks.
  171. (union-build output (append extra-inputs inputs)
  172. #:symlink symlink
  173. #:log-port (%make-void-port "w"))
  174. ;; If one of the INPUTS provides a '/manifest' file, delete it. That can
  175. ;; happen if MANIFEST contains something such as a Guix instance, which is
  176. ;; ultimately built as a profile.
  177. (when (file-exists? manifest-file)
  178. (delete-file manifest-file))
  179. ;; Store meta-data.
  180. (call-with-output-file manifest-file
  181. (lambda (p)
  182. (display "\
  183. ;; This file was automatically generated and is for internal use only.
  184. ;; It cannot be passed to the '--manifest' option.
  185. ;; Run 'guix package --export-manifest' if you want to export a file
  186. ;; suitable for '--manifest'.\n\n"
  187. p)
  188. (pretty-print manifest p)))
  189. ;; Make sure we can write to 'OUTPUT/etc'. 'union-build' above could have
  190. ;; made 'etc' a symlink to a read-only sub-directory in the store so we
  191. ;; need to work around that.
  192. (ensure-writable-directory (string-append output "/etc")
  193. #:symlink symlink)
  194. ;; Write 'OUTPUT/etc/profile'.
  195. (build-etc/profile output search-paths)))
  196. ;;; profile.scm ends here