Subject: Re: bug#52808: Guix home should not assume that all targets are dot files

From: Andrew Tropin <[email-masked]>
Date: Fri, 11 Feb 2022 18:52:36 +0300

On 2022-02-08 10:46, Ludovic Courtès wrote:

> Hi,
>
> Andrew Tropin <[email-masked]> skribis:
>
>>>> You can elaborate more on what you try to achieve and I can try to give
>>>> you a recommendation how to implement it.
>>>
>>> I’d expect ‘home-files-service-type’ to do just that: add files to the
>>> home directory, without trying to be smart.
>>>
>>> Would it make sense to distinguish between ‘home-files’ and (say)
>>> ‘home-xdg-configuration-files’?
>>
>> Yep, I can do that, actually, it will be even better for the purpose I
>> originally had.  I'll make home-files to store files as it is and
>> symlink manager not to add leading dots and a separate folder for
>> xdg configs.
>
> Neat.
>
>> Ludo, Nick, what do you think about following names?
>> ~/.guix-home/home-dir-files/
>> ~/.guix-home/xdg-config-dir-files/
>
> I’d make it ‘…/home-files’ and ‘…/xdg-configuration-files’, but that’s a
> detail.
>
>>> I’d also suggest removing special handling of HOME/files in
>>> symlink-manager.scm.  Relations between the various components of Guix
>>> Home should preferably be made explicit via service extensions, and not
>>> implicit through conventions like this ‘files’ sub-directory.
>>>
>>> Thoughts?
>>
>> Unfortunatelly, I don't know how to implement polymorphic behavior the
>> other way with current extension mechanism, so I would prefer to keep
>> this relation implicit,
>
> I’m not sure I follow but maybe I should try by myself to get a better
> understanding.
>
> Thanks for your feedback!
>
> Ludo’.

I decided to go one step at a time, and prepared a patch series, which:

1. Adds an explicit connection between home-files-service-type and
symlink-manager by introducing a global constant used by both services.

2. Adds a home-xdg-configuration-files-service-type, which accepts a
list of files for XDG_CONFIG_DIR, `(("mpv/mpv.conf" ,file-like-here))

3. Migrates all (gnu home services) to xdg-configuration-files.

4. Make symlink-manager respect XDG_CONIFG_HOME and
xdg-configuration-files-subdir.

After that patch series is merged we can give a time for users to
migrate their self-made home services to xdg-configuration-files and
after for example 2 weeks, remove special handling of dots for
home-files.

From 0cd37bbc724f9c793898c2655bdd1c335045c5f0 Mon Sep 17 00:00:00 2001
From: Andrew Tropin <[email-masked]>
Date: Fri, 11 Feb 2022 10:55:01 +0300
Subject: [PATCH 1/5] home: Explicitly connect home-file and symlink-manager
 services.

* gnu/home/services.scm (home-files-directory): New variable.
* gnu/home/symlink-manager.scm (update-symlinks-script): Use
home-files-directory variable from (gnu home services).
---
 gnu/home/services.scm                 | 23 ++++++++++++++---------
 gnu/home/services/symlink-manager.scm | 17 +++++++++--------
 2 files changed, 23 insertions(+), 17 deletions(-)

diff --git a/gnu/home/services.scm b/gnu/home/services.scm
index 1cd19ce7f9..e4e3717b80 100644
--- a/gnu/home/services.scm
+++ b/gnu/home/services.scm
@@ -43,6 +43,8 @@ (define-module (gnu home services)
             home-run-on-change-service-type
             home-provenance-service-type
 
+            home-files-directory
+
             fold-home-service-types
             home-provenance
 
@@ -74,12 +76,11 @@ (define-module (gnu home services)
 ;;; file (details described in the manual).
 ;;;
 ;;; home-files-service-type is similar to etc-service-type, but doesn't extend
-;;; home-activation, because deploy mechanism for config files is pluggable and
-;;; can be different for different home environments: The default one is called
-;;; symlink-manager (will be introudced in a separate patch series), which creates
-;;; links for various dotfiles (like $XDG_CONFIG_HOME/$APP/...) to store, but is
-;;; possible to implement alternative approaches like read-only home from Julien's
-;;; guix-home-manager.
+;;; home-activation, because deploy mechanism for config files is pluggable
+;;; and can be different for different home environments: The default one is
+;;; called symlink-manager, which creates links for various dotfiles and xdg
+;;; configuration files to store, but is possible to implement alternative
+;;; approaches like read-only home from Julien's guix-home-manager.
 ;;;
 ;;; home-run-on-first-login-service-type provides an @file{on-first-login} guile
 ;;; script, which runs provided gexps once, when user makes first login.  It can
@@ -262,11 +263,14 @@ (define (assert-no-duplicates files)
 
   (file-union "files" files))
 
+;; Used by symlink-manager
+(define home-files-directory "files")
+
 (define (files-entry files)
   "Return an entry for the @file{~/.guix-home/files}
 directory containing FILES."
   (with-monad %store-monad
-    (return `(("files" ,(files->files-directory files))))))
+    (return `((,home-files-directory ,(files->files-directory files))))))
 
 (define home-files-service-type
   (service-type (name 'home-files)
@@ -276,8 +280,9 @@ (define home-files-service-type
                 (compose concatenate)
                 (extend append)
                 (default-value '())
-                (description "Configuration files for programs that
-will be put in @file{~/.guix-home/files}.")))
+                (description (format #f "Files that will be put in
+@file{~~/.guix-home/~a}, and further processed during activation."
+                                     home-files-directory))))
 
 (define %initialize-gettext
   #~(begin
diff --git a/gnu/home/services/symlink-manager.scm b/gnu/home/services/symlink-manager.scm
index 314da3ba3e..747bb343d3 100644
--- a/gnu/home/services/symlink-manager.scm
+++ b/gnu/home/services/symlink-manager.scm
@@ -25,12 +25,11 @@ (define-module (gnu home services symlink-manager)
 
 ;;; Comment:
 ;;;
-;;; symlink-manager cares about configuration files: it backs up files
-;;; created by user, removes symlinks and directories created by a
-;;; previous generation, and creates new directories and symlinks to
-;;; configuration files according to the content of files/ directory
-;;; (created by home-files-service) of the current home environment
-;;; generation.
+;;; symlink-manager cares about xdg configurations and other files: it backs
+;;; up files created by user, removes symlinks and directories created by a
+;;; previous generation, and creates new directories and symlinks to files
+;;; according to the content of directories (created by home-files-service) of
+;;; the current home environment generation.
 ;;;
 ;;; Code:
 
@@ -94,7 +93,8 @@ (define ((file-tree-traverse preordering) node)
               (new-home (getenv "GUIX_NEW_HOME"))
               (old-home (getenv "GUIX_OLD_HOME"))
 
-              (new-files-path (string-append new-home "/files"))
+              (new-files-path (string-append
+                               new-home "/" #$home-files-directory))
               ;; Trailing dot is required, because files itself is symlink and
               ;; to make file-system-tree works it should be a directory.
               (new-files-dir-path (string-append new-files-path "/."))
@@ -107,7 +107,8 @@ (define ((file-tree-traverse preordering) node)
               (old-tree (if old-home
                           ((simplify-file-tree "")
                            (file-system-tree
-                            (string-append old-home "/files/.")))
+                            (string-append
+                             old-home "/" #$home-files-directory "/.")))
                           #f))
               (new-tree ((simplify-file-tree "")
                          (file-system-tree new-files-dir-path)))
-- 
2.34.0

From 23f7095d60b18b52de0d1aa314c4012cdf55a046 Mon Sep 17 00:00:00 2001
From: Andrew Tropin <[email-masked]>
Date: Fri, 11 Feb 2022 11:03:02 +0300
Subject: [PATCH 2/5] home: Add home-xdg-configuration-files service.

* gnu/home/services.scm (home-xdg-configuration-files): New variable.
---
 gnu/home/services.scm | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/gnu/home/services.scm b/gnu/home/services.scm
index e4e3717b80..bf044a0421 100644
--- a/gnu/home/services.scm
+++ b/gnu/home/services.scm
@@ -38,12 +38,14 @@ (define-module (gnu home services)
             home-profile-service-type
             home-environment-variables-service-type
             home-files-service-type
+            home-xdg-configuration-files-service-type
             home-run-on-first-login-service-type
             home-activation-service-type
             home-run-on-change-service-type
             home-provenance-service-type
 
             home-files-directory
+            xdg-configuration-files-subdir
 
             fold-home-service-types
             home-provenance
@@ -284,6 +286,27 @@ (define home-files-service-type
 @file{~~/.guix-home/~a}, and further processed during activation."
                                      home-files-directory))))
 
+(define xdg-configuration-files-subdir "config")
+
+(define (xdg-configuration-files files)
+  (map (lambda (lst)
+         (cons (string-append xdg-configuration-files-subdir
+                              "/" (car lst)) (cdr lst)))
+         files))
+
+(define home-xdg-configuration-files-service-type
+  (service-type (name 'home-files)
+                (extensions
+                 (list (service-extension home-files-service-type
+                                          xdg-configuration-files)))
+                (compose concatenate)
+                (extend append)
+                (default-value '())
+                (description (format #f "Files that will be put in
+@file{~~/.guix-home/~a/~a}, and further processed during activation."
+                                     home-files-directory
+                                     xdg-configuration-files))))
+
 (define %initialize-gettext
   #~(begin
       (bindtextdomain %gettext-domain
-- 
2.34.0

From 11f23a48d480a91d6bfba0ff55c1a9831585a4ee Mon Sep 17 00:00:00 2001
From: Andrew Tropin <[email-masked]>
Date: Fri, 11 Feb 2022 15:03:44 +0300
Subject: [PATCH 3/5] home: shells: Migrate zsh to xdg-configuration-files.

* gnu/home/services.scm (home-zsh-service-type): Additionally extend
home-xdg-configuration-files-service-type.
---
 gnu/home/services/shells.scm | 112 +++++++++++++++++++----------------
 1 file changed, 61 insertions(+), 51 deletions(-)

diff --git a/gnu/home/services/shells.scm b/gnu/home/services/shells.scm
index ca7f4ac0ad..4b3618a868 100644
--- a/gnu/home/services/shells.scm
+++ b/gnu/home/services/shells.scm
@@ -171,56 +171,27 @@ (define-configuration home-zsh-configuration
 won't be read in some cases (if the shell terminates by exec'ing
 another process for example)."))
 
-(define (add-zsh-configuration config)
-  (let* ((xdg-flavor? (home-zsh-configuration-xdg-flavor? config)))
+(define (zsh-filter-fields field)
+  (filter-configuration-fields home-zsh-configuration-fields (list field)))
 
-    (define prefix-file
-      (cut string-append
-        (if xdg-flavor?
-            "config/zsh/."
-            "") <>))
+(define (zsh-serialize-field config field)
+  (serialize-configuration config (zsh-filter-fields field)))
 
-    (define (filter-fields field)
-      (filter-configuration-fields home-zsh-configuration-fields
-                                   (list field)))
+(define* (zsh-field-not-empty? config field)
+  (let ((file-name (symbol->string field))
+        (field-obj (car (zsh-filter-fields field))))
+    (not (null? ((configuration-field-getter field-obj) config)))))
 
-    (define (serialize-field field)
-      (serialize-configuration
-       config
-       (filter-fields field)))
+(define (zsh-file-zshenv config)
+  (mixed-text-file
+   "zshenv"
+   (zsh-serialize-field config 'zshenv)
+   (zsh-serialize-field config 'environment-variables)))
 
-    (define (file-if-not-empty field)
-      (let ((file-name (symbol->string field))
-            (field-obj (car (filter-fields field))))
-        (if (not (null? ((configuration-field-getter field-obj) config)))
-            `(,(prefix-file file-name)
-              ,(mixed-text-file
-                file-name
-                (serialize-field field)))
-            '())))
-
-    (filter
-     (compose not null?)
-     `(,(if xdg-flavor?
-            `("zshenv"
-              ,(mixed-text-file
-                "auxiliary-zshenv"
-                (if xdg-flavor?
-                    "source ${XDG_CONFIG_HOME:-$HOME/.config}/zsh/.zshenv\n"
-                    "")))
-            '())
-       (,(prefix-file "zshenv")
-        ,(mixed-text-file
-          "zshenv"
-          (if xdg-flavor?
-              "export ZDOTDIR=${XDG_CONFIG_HOME:-$HOME/.config}/zsh\n"
-              "")
-          (serialize-field 'zshenv)
-          (serialize-field 'environment-variables)))
-       (,(prefix-file "zprofile")
-        ,(mixed-text-file
-          "zprofile"
-          "\
+(define (zsh-file-zprofile config)
+  (mixed-text-file
+   "zprofile"
+   "\
 # Setups system and user profiles and related variables
 source /etc/profile
 # Setups home environment profile
@@ -229,11 +200,47 @@ (define (file-if-not-empty field)
 # It's only necessary if zsh is a login shell, otherwise profiles will
 # be already sourced by bash
 "
-          (serialize-field 'zprofile)))
+   (zsh-serialize-field config 'zprofile)))
 
-       ,@(list (file-if-not-empty 'zshrc)
-               (file-if-not-empty 'zlogin)
-               (file-if-not-empty 'zlogout))))))
+(define (zsh-file-by-field config field)
+  (match field
+    ('zshenv (zsh-file-zshenv config))
+    ('zprofile (zsh-file-zprofile config))
+    (e (mixed-text-file
+        (symbol->string field)
+        (zsh-serialize-field config field)))))
+
+(define (zsh-get-configuration-files config)
+  `(("zprofile" ,(zsh-file-by-field config 'zprofile)) ;; Always non-empty
+    ,@(if (and (zsh-field-not-empty? config 'zshenv)
+               (zsh-field-not-empty? config 'environment-variables))
+          `(("zshenv" ,(zsh-file-by-field config 'zshenv))) '())
+    ,@(if (zsh-field-not-empty? config 'zshrc)
+          `(("zshrc" ,(zsh-file-by-field config 'zshrc))) '())
+    ,@(if (zsh-field-not-empty? config 'zlogin)
+          `(("zlogin" ,(zsh-file-by-field config 'zlogin))) '())
+    ,@(if (zsh-field-not-empty? config 'zlogout)
+          `(("zlogout" ,(zsh-file-by-field config 'zlogout))) '())))
+
+(define (zsh-home-files config)
+  (define zshenv-auxiliary-file
+    (mixed-text-file
+     "zshenv-auxiliary"
+     "export ZDOTDIR=${XDG_CONFIG_HOME:-$HOME/.config}/zsh\n"
+     "[[ -f $ZDOTDIR/.zshenv ]] && source $ZDOTDIR/.zshenv\n"))
+
+  (if (home-zsh-configuration-xdg-flavor? config)
+      `(("zshenv" ,zshenv-auxiliary-file))
+      (zsh-get-configuration-files config)))
+
+(define (zsh-xdg-configuration-files config)
+  (if (home-zsh-configuration-xdg-flavor? config)
+      (map
+       (lambda (lst)
+         (cons (string-append "zsh/." (car lst))
+               (cdr lst)))
+       (zsh-get-configuration-files config))
+      '()))
 
 (define (add-zsh-packages config)
   (list (home-zsh-configuration-package config)))
@@ -291,7 +298,10 @@ (define home-zsh-service-type
                 (extensions
                  (list (service-extension
                         home-files-service-type
-                        add-zsh-configuration)
+                        zsh-home-files)
+                       (service-extension
+                        home-xdg-configuration-files-service-type
+                        zsh-xdg-configuration-files)
                        (service-extension
                         home-profile-service-type
                         add-zsh-packages)))
-- 
2.34.0

From ef4c3bbcc0c8c1a251f4ad6c494f8ed30adf45f2 Mon Sep 17 00:00:00 2001
From: Andrew Tropin <[email-masked]>
Date: Fri, 11 Feb 2022 15:34:46 +0300
Subject: [PATCH 4/5] home: Migrate fountutils and xdg modules to
 xdg-configuration-files.

* gnu/home/services/fontutils.scm (home-fontconfig-service-type): Migrate to
xdg-configuration-files.
* gnu/home/services/xdg.scm (home-xdg-user-directories-service-type,
home-xdg-mime-applications-service-type): Migrate to xdg-configuration-files.
---
 gnu/home/services/fontutils.scm |  4 ++--
 gnu/home/services/xdg.scm       | 31 +++++++++++++++++--------------
 2 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/gnu/home/services/fontutils.scm b/gnu/home/services/fontutils.scm
index 772904367d..6062eaed6a 100644
--- a/gnu/home/services/fontutils.scm
+++ b/gnu/home/services/fontutils.scm
@@ -34,7 +34,7 @@ (define-module (gnu home services fontutils)
 ;;; Code:
 
 (define (add-fontconfig-config-file he-symlink-path)
-  `(("config/fontconfig/fonts.conf"
+  `(("fontconfig/fonts.conf"
      ,(mixed-text-file
        "fonts.conf"
        "<?xml version='1.0'?>
@@ -51,7 +51,7 @@ (define home-fontconfig-service-type
   (service-type (name 'home-fontconfig)
                 (extensions
                  (list (service-extension
-                        home-files-service-type
+                        home-xdg-configuration-files-service-type
                         add-fontconfig-config-file)
                        (service-extension
                         home-run-on-change-service-type
diff --git a/gnu/home/services/xdg.scm b/gnu/home/services/xdg.scm
index d230dd7665..9c43aa93b9 100644
--- a/gnu/home/services/xdg.scm
+++ b/gnu/home/services/xdg.scm
@@ -190,11 +190,11 @@ (define-configuration home-xdg-user-directories-configuration
    "Default directory for videos."))
 
 (define (home-xdg-user-directories-files-service config)
-  `(("config/user-dirs.conf"
+  `(("user-dirs.conf"
      ,(mixed-text-file
        "user-dirs.conf"
        "enabled=False\n"))
-    ("config/user-dirs.dirs"
+    ("user-dirs.dirs"
      ,(mixed-text-file
        "user-dirs.dirs"
       (serialize-configuration
@@ -218,7 +218,7 @@ (define home-xdg-user-directories-service-type
   (service-type (name 'home-xdg-user-directories)
                 (extensions
                  (list (service-extension
-                        home-files-service-type
+                        home-xdg-configuration-files-service-type
                         home-xdg-user-directories-files-service)
                        (service-extension
                         home-activation-service-type
@@ -417,7 +417,7 @@ (define-configuration home-xdg-mime-applications-configuration
    "A list of XDG desktop entries to create.  See
 @code{xdg-desktop-entry}."))
 
-(define (home-xdg-mime-applications-files-service config)
+(define (home-xdg-mime-applications-files config)
   (define (add-xdg-desktop-entry-file entry)
     (let ((file (first entry))
           (config (second entry)))
@@ -425,16 +425,16 @@ (define (add-xdg-desktop-entry-file entry)
           (apply mixed-text-file
                  (format #f "xdg-desktop-~a-entry" file)
                  config))))
+  (map (compose add-xdg-desktop-entry-file serialize-xdg-desktop-entry)
+       (home-xdg-mime-applications-configuration-desktop-entries config)))
 
-  (append
-   `(("config/mimeapps.list"
-      ,(mixed-text-file
-        "xdg-mime-appplications"
-        (serialize-configuration
-         config
-         home-xdg-mime-applications-configuration-fields))))
-   (map (compose add-xdg-desktop-entry-file serialize-xdg-desktop-entry)
-        (home-xdg-mime-applications-configuration-desktop-entries config))))
+(define (home-xdg-mime-applications-xdg-files config)
+  `(("mimeapps.list"
+     ,(mixed-text-file
+       "xdg-mime-appplications"
+       (serialize-configuration
+        config
+        home-xdg-mime-applications-configuration-fields)))))
 
 (define (home-xdg-mime-applications-extension old-config extension-configs)
   (define (extract-fields config)
@@ -469,7 +469,10 @@ (define home-xdg-mime-applications-service-type
                 (extensions
                  (list (service-extension
                         home-files-service-type
-                        home-xdg-mime-applications-files-service)))
+                        home-xdg-mime-applications-files)
+                       (service-extension
+                        home-xdg-configuration-files-service-type
+                        home-xdg-mime-applications-xdg-files)))
                 (compose identity)
                 (extend home-xdg-mime-applications-extension)
                 (default-value (home-xdg-mime-applications-configuration))
-- 
2.34.0

From 089683bbd301f6e085f00fbd53713f335abac40e Mon Sep 17 00:00:00 2001
From: Andrew Tropin <[email-masked]>
Date: Fri, 11 Feb 2022 16:14:23 +0300
Subject: [PATCH 5/5] home: symlink-manager: Respect XDG_CONFIG_HOME during
 activation.

* gnu/home/services/symlink-manager.scm (update-symlinks-script): Respect
XDG_CONFIG_HOME during activation.
---
 gnu/home/services/symlink-manager.scm | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/gnu/home/services/symlink-manager.scm b/gnu/home/services/symlink-manager.scm
index 747bb343d3..418bfbd98a 100644
--- a/gnu/home/services/symlink-manager.scm
+++ b/gnu/home/services/symlink-manager.scm
@@ -85,8 +85,8 @@ (define ((file-tree-traverse preordering) node)
 
        (use-modules (guix build utils))
 
-       (let* ((config-home    (or (getenv "XDG_CONFIG_HOME")
-                                  (string-append (getenv "HOME") "/.config")))
+       (let* ((xdg-config-home (or (getenv "XDG_CONFIG_HOME")
+                                   (string-append (getenv "HOME") "/.config")))
 
               (he-path (string-append (getenv "HOME") "/.guix-home"))
               (new-he-path (string-append he-path ".new"))
@@ -117,13 +117,24 @@ (define ((file-tree-traverse preordering) node)
                (lambda (path)
                  (readlink (string-append new-files-path "/" path))))
 
+              (preprocess-path
+               (lambda (path)
+                 "If file is in xdg-configuration-files-subdir use
+subdirectory from XDG_CONFIG_HOME to generate a target path."
+                 (if (string-prefix? #$xdg-configuration-files-subdir path)
+                     (string-append
+                      (substring xdg-config-home (1+ (string-length home-path)))
+                      (substring
+                       path (string-length #$xdg-configuration-files-subdir)))
+                     (string-append "." path))))
+
               (get-target-path
                (lambda (path)
-                 (string-append home-path "/." path)))
+                 (string-append home-path "/" (preprocess-path path))))
 
               (get-backup-path
                (lambda (path)
-                 (string-append backup-dir "/." path)))
+                 (string-append backup-dir "/" (preprocess-path path))))
 
               (directory?
                (lambda (path)
@@ -224,6 +235,12 @@ (define ((file-tree-traverse preordering) node)
                        (display (G_ " done\n"))))
                     to-create)))))
 
+         (format #t "home-path: ~a\nxdg-config-home: ~a\n"
+                 home-path xdg-config-home)
+
+         (format #t "prepr: ~a\n"
+                 (preprocess-path "config/sway/config"))
+
          (when old-tree
            (cleanup-symlinks))
 
-- 
2.34.0

-- 
Best regards,
Andrew Tropin