diff options
Diffstat (limited to 'guix/scripts/home/import.scm')
-rw-r--r-- | guix/scripts/home/import.scm | 301 |
1 files changed, 110 insertions, 191 deletions
diff --git a/guix/scripts/home/import.scm b/guix/scripts/home/import.scm index 611f580e85..7a7712dd96 100644 --- a/guix/scripts/home/import.scm +++ b/guix/scripts/home/import.scm @@ -1,6 +1,7 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz> ;;; Copyright © 2021 Andrew Tropin <andrew@trop.in> +;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org> ;;; ;;; This file is part of GNU Guix. ;;; @@ -22,12 +23,16 @@ #:use-module (guix ui) #:use-module (guix utils) #:use-module (guix packages) + #:autoload (guix scripts package) (manifest-entry-version-prefix) #:use-module (gnu packages) #:use-module (ice-9 match) #:use-module (ice-9 pretty-print) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) - #:export (import-manifest)) + #:export (import-manifest + + ;; For tests. + manifest+configuration-files->code)) ;;; Commentary: ;;; @@ -36,200 +41,114 @@ ;;; ;;; Code: - -(define (generate-bash-module+configuration) - (let ((rc (string-append (getenv "HOME") "/.bashrc")) - (profile (string-append (getenv "HOME") "/.bash_profile")) - (logout (string-append (getenv "HOME") "/.bash_logout"))) - `((gnu home services bash) - (service home-bash-service-type - (home-bash-configuration - ,@(if (file-exists? rc) - `((bashrc - (list (local-file ,rc)))) - '()) - ,@(if (file-exists? profile) - `((bash-profile - (list (local-file ,profile)))) - '()) - ,@(if (file-exists? logout) - `((bash-logout - (list (local-file ,logout)))) - '())))))) - - -(define %files-configurations-alist - `((".bashrc" . ,generate-bash-module+configuration) - (".bash_profile" . ,generate-bash-module+configuration) - (".bash_logout" . ,generate-bash-module+configuration))) - -(define (modules+configurations) - (let ((configurations (delete-duplicates - (filter-map (match-lambda - ((file . proc) - (if (file-exists? - (string-append (getenv "HOME") "/" file)) - proc - #f))) - %files-configurations-alist) - (lambda (x y) - (equal? (procedure-name x) (procedure-name y)))))) - (map (lambda (proc) (proc)) configurations))) - -;; Based on `manifest->code' from (guix profiles) -;; MAYBE: Upstream it? -(define* (manifest->code manifest - #:key - (entry-package-version (const "")) - (home-environment? #f)) - "Return an sexp representing code to build an approximate version of -MANIFEST; the code is wrapped in a top-level 'begin' form. If -HOME-ENVIRONMENT? is #t, return an <home-environment> definition. -Call ENTRY-PACKAGE-VERSION to determine the version number to use in -the spec for a given entry; it can be set to 'manifest-entry-version' -for fully-specified version numbers, or to some other procedure to -disambiguate versions for packages for which several versions are -available." - (define (entry-transformations entry) - ;; Return the transformations that apply to ENTRY. - (assoc-ref (manifest-entry-properties entry) 'transformations)) - - (define transformation-procedures - ;; List of transformation options/procedure name pairs. - (let loop ((entries (manifest-entries manifest)) - (counter 1) - (result '())) - (match entries - (() result) - ((entry . tail) - (match (entry-transformations entry) - (#f - (loop tail counter result)) - (options - (if (assoc-ref result options) - (loop tail counter result) - (loop tail (+ 1 counter) - (alist-cons options - (string->symbol - (format #f "transform~a" counter)) - result))))))))) - - (define (qualified-name entry) - ;; Return the name of ENTRY possibly with "@" followed by a version. - (match (entry-package-version entry) - ("" (manifest-entry-name entry)) - (version (string-append (manifest-entry-name entry) - "@" version)))) - - (if (null? transformation-procedures) - (let ((specs (map (lambda (entry) - (match (manifest-entry-output entry) - ("out" (qualified-name entry)) - (output (string-append (qualified-name entry) - ":" output)))) - (manifest-entries manifest)))) - (if home-environment? - (let ((modules+configurations (modules+configurations))) - `(begin - (use-modules (gnu home) - (gnu packages) - ,@(map first modules+configurations)) - ,(home-environment-template - #:specs specs - #:services (map second modules+configurations)))) - `(begin - (use-modules (gnu packages)) - - (specifications->manifest - (list ,@specs))))) - (let* ((transform (lambda (options exp) - (if (not options) - exp - (let ((proc (assoc-ref transformation-procedures - options))) - `(,proc ,exp))))) - (packages (map (lambda (entry) - (define options - (entry-transformations entry)) - - (define name - (qualified-name entry)) - - (match (manifest-entry-output entry) - ("out" - (transform options - `(specification->package ,name))) - (output - `(list ,(transform - options - `(specification->package ,name)) - ,output)))) - (manifest-entries manifest))) - (transformations (map (match-lambda - ((options . name) - `(define ,name - (options->transformation ',options)))) - transformation-procedures))) - (if home-environment? - (let ((modules+configurations (modules+configurations))) - `(begin - (use-modules (guix transformations) - (gnu home) - (gnu packages) - ,@(map first modules+configurations)) - - ,@transformations - - ,(home-environment-template - #:packages packages - #:services (map second modules+configurations)))) - `(begin - (use-modules (guix transformations) - (gnu packages)) - - ,@transformations - - (packages->manifest - (list ,@packages))))))) - -(define* (home-environment-template #:key (packages #f) (specs #f) services) - "Return an S-exp containing a <home-environment> declaration -containing PACKAGES, or SPECS (package specifications), and SERVICES." - `(home-environment - (packages - ,@(if packages - `((list ,@packages)) - `((map specification->package - (list ,@specs))))) - (services (list ,@services)))) +(define (basename+remove-dots file-name) + "Remove the dot from the dotfile FILE-NAME; replace the other dots in +FILE-NAME with \"-\", and return the basename of it." + (string-map (match-lambda + (#\. #\-) + (c c)) + (let ((base (basename file-name))) + (if (string-prefix? "." base) + (string-drop base 1) + base)))) + +(define (generate-bash-configuration+modules destination-directory) + (define (destination-append path) + (string-append destination-directory "/" path)) + + (let ((rc (destination-append ".bashrc")) + (profile (destination-append ".bash_profile")) + (logout (destination-append ".bash_logout"))) + `((service home-bash-service-type + (home-bash-configuration + ,@(if (file-exists? rc) + `((bashrc + (list (local-file ,rc + ,(basename+remove-dots rc))))) + '()) + ,@(if (file-exists? profile) + `((bash-profile + (list (local-file ,profile + ,(basename+remove-dots profile))))) + '()) + ,@(if (file-exists? logout) + `((bash-logout + (list (local-file ,logout + ,(basename+remove-dots logout))))) + '()))) + (guix gexp) + (gnu home services shells)))) + +(define %files+configurations-alist + `((".bashrc" . ,generate-bash-configuration+modules) + (".bash_profile" . ,generate-bash-configuration+modules) + (".bash_logout" . ,generate-bash-configuration+modules))) + +(define (configurations+modules configuration-directory) + "Return a list of procedures which when called, generate code for a home +service declaration. Copy configuration files to CONFIGURATION-DIRECTORY; the +generated service declarations will refer to those files that have been saved +in CONFIGURATION-DIRECTORY." + (define configurations + (delete-duplicates + (filter-map (match-lambda + ((file . proc) + (let ((absolute-path (string-append (getenv "HOME") + "/" file))) + (and (file-exists? absolute-path) + (begin + (copy-file absolute-path + (string-append + configuration-directory "/" file)) + proc))))) + %files+configurations-alist) + eq?)) + + (map (lambda (proc) (proc configuration-directory)) configurations)) + +(define (manifest+configuration-files->code manifest + configuration-directory) + "Read MANIFEST and the user's configuration files listed in +%FILES+CONFIGURATIONS-ALIST, and return a 'home-environment' sexp. Copy the +user's files to CONFIGURATION-DIRECTORY; the generated sexp refers to them." + (match (manifest->code manifest + #:entry-package-version + manifest-entry-version-prefix) + (('begin ('use-modules profile-modules ...) + definitions ... ('packages->manifest packages)) + (match (configurations+modules configuration-directory) + (((services . modules) ...) + `(begin + (use-modules (gnu home) + (gnu packages) + (gnu services) + ,@(delete-duplicates + (append profile-modules (concatenate modules)))) + + ,@definitions + + (home-environment + (packages ,packages) + (services (list ,@services))))))) + (('begin ('specifications->manifest packages)) + (match (configurations+modules configuration-directory) + (((services . modules) ...) + `(begin + (use-modules (gnu home) + (gnu packages) + (gnu services) + ,@(delete-duplicates (concatenate modules))) + + (home-environment + (packages (map specification->package ,packages)) + (services (list ,@services))))))))) (define* (import-manifest - manifest + manifest destination-directory #:optional (port (current-output-port))) "Write to PORT a <home-environment> corresponding to MANIFEST." - (define (version-spec entry) - (let ((name (manifest-entry-name entry))) - (match (map package-version (find-packages-by-name name)) - ((_) - ;; A single version of NAME is available, so do not specify the - ;; version number, even if the available version doesn't match ENTRY. - "") - (versions - ;; If ENTRY uses the latest version, don't specify any version. - ;; Otherwise return the shortest unique version prefix. Note that - ;; this is based on the currently available packages, which could - ;; differ from the packages available in the revision that was used - ;; to build MANIFEST. - (let ((current (manifest-entry-version entry))) - (if (every (cut version>? current <>) - (delete current versions)) - "" - (version-unique-prefix (manifest-entry-version entry) - versions))))))) - - (match (manifest->code manifest - #:entry-package-version version-spec - #:home-environment? #t) + (match (manifest+configuration-files->code manifest + destination-directory) (('begin exp ...) (format port (G_ "\ ;; This \"home-environment\" file can be passed to 'guix home reconfigure' |