~ruther/guix-local

34760ae7031b19529aff2355cc458f61a8daa41f — Ludovic Courtès 10 years ago 137d957
system: Add 'locale-libcs' field.

* gnu/system/locale.scm (localedef-command)[maybe-version-directory]:
  New procedure.
  Use it.
  (locale-directory): Rename to...
  (single-locale-directory): ... this.  Check the version of LIBC to
  determine whether to create a "X.Y" sub-directory or to make it a
  symlink to ".".  Add the version number in the derivation name.
  (locale-directory): New procedure.
  (%default-locale-libcs): New variable.
* gnu/system.scm (<operating-system>)[locale-libcs]: New field.
  (operating-system-locale-directory): Pass it to 'locale-directory'.
* doc/guix.texi (operating-system Reference): Document 'locale-libcs'.
  (Locales)[Locale Data Compatibility Considerations]: New section.
3 files changed, 118 insertions(+), 6 deletions(-)

M doc/guix.texi
M gnu/system.scm
M gnu/system/locale.scm
M doc/guix.texi => doc/guix.texi +56 -0
@@ 5562,6 5562,11 @@ Library Reference Manual}).  @xref{Locales}, for more information.
The list of locale definitions to be compiled and that may be used at
run time.  @xref{Locales}.

@item @code{locale-libcs} (default: @code{(list @var{glibc})})
The list of GNU@tie{}libc packages whose locale data and tools are used
to build the locale definitions.  @xref{Locales}, for compatibility
considerations that justify this option.

@item @code{name-service-switch} (default: @var{%default-nss})
Configuration of libc's name service switch (NSS)---a
@code{<name-service-switch>} object.  @xref{Name Service Switch}, for


@@ 6020,6 6025,57 @@ instance it has @code{uk_UA.utf8} but @emph{not}, say,
@code{uk_UA.UTF-8}.
@end defvr

@subsubsection Locale Data Compatibility Considerations

@cindex incompatibility, of locale data
@code{operating-system} declarations provide a @code{locale-libcs} field
to specify the GNU@tie{}libc packages that are used to compile locale
declarations (@pxref{operating-system Reference}).  ``Why would I
care?'', you may ask.  Well, it turns out that the binary format of
locale data is occasionally incompatible from one libc version to
another.

@c See <https://sourceware.org/ml/libc-alpha/2015-09/msg00575.html>
@c and <https://lists.gnu.org/archive/html/guix-devel/2015-08/msg00737.html>.
For instance, a program linked against libc version 2.21 is unable to
read locale data produced with libc 2.22; worse, that program
@emph{aborts} instead of simply ignoring the incompatible locale
data@footnote{Versions 2.23 and later of GNU@tie{}libc will simply skip
the incompatible locale data, which is already an improvement.}.
Similarly, a program linked against libc 2.22 can read most, but not
all, the locale data from libc 2.21 (specifically, @code{LC_COLLATE}
data is incompatible); thus calls to @code{setlocale} may fail, but
programs will not abort.

The ``problem'' in GuixSD is that users have a lot of freedom: They can
choose whether and when to upgrade software in their profiles, and might
be using a libc version different from the one the system administrator
used to build the system-wide locale data.

Fortunately, unprivileged users can also install their own locale data
and define @var{GUIX_LOCPATH} accordingly (@pxref{locales-and-locpath,
@code{GUIX_LOCPATH} and locale packages}).

Still, it is best if the system-wide locale data at
@file{/run/current-system/locale} is built for all the libc versions
actually in use on the system, so that all the programs can access
it---this is especially crucial on a multi-user system.  To do that, the
administrator can specify several libc packages in the
@code{locale-libcs} field of @code{operating-system}:

@example
(use-package-modules base)

(operating-system
  ;; @dots{}
  (locale-libcs (list glibc-2.21 (canonical-package glibc))))
@end example

This example would lead to a system containing locale definitions for
both libc 2.21 and the current version of libc in
@file{/run/current-system/locale}.


@node Services
@subsection Services


M gnu/system.scm => gnu/system.scm +5 -1
@@ 76,6 76,7 @@
            operating-system-timezone
            operating-system-locale
            operating-system-locale-definitions
            operating-system-locale-libcs
            operating-system-mapped-devices
            operating-system-file-systems
            operating-system-activation-script


@@ 144,6 145,8 @@
            (default "en_US.utf8"))
  (locale-definitions operating-system-locale-definitions ; list of <locale-definition>
                      (default %default-locale-definitions))
  (locale-libcs operating-system-locale-libcs     ; list of <packages>
                (default %default-locale-libcs))
  (name-service-switch operating-system-name-service-switch ; <name-service-switch>
                       (default %default-nss))



@@ 643,7 646,8 @@ listed in OS.  The C library expects to find it under
    (raise (condition
            (&message (message "system locale lacks a definition")))))

  (locale-directory (operating-system-locale-definitions os)))
  (locale-directory (operating-system-locale-definitions os)
                    #:libcs (operating-system-locale-libcs os)))

(define (kernel->grub-label kernel)
  "Return a label for the GRUB menu entry that boots KERNEL."

M gnu/system/locale.scm => gnu/system/locale.scm +57 -5
@@ 18,11 18,15 @@

(define-module (gnu system locale)
  #:use-module (guix gexp)
  #:use-module (guix store)
  #:use-module (guix monads)
  #:use-module (guix records)
  #:use-module (guix packages)
  #:use-module (guix utils)
  #:use-module (gnu packages base)
  #:use-module (gnu packages compression)
  #:use-module (srfi srfi-26)
  #:use-module (ice-9 match)
  #:export (locale-definition
            locale-definition?
            locale-definition-name


@@ 31,6 35,7 @@

            locale-directory

            %default-locale-libcs
            %default-locale-definitions))

;;; Commentary:


@@ 50,6 55,15 @@
(define* (localedef-command locale
                            #:key (libc (canonical-package glibc)))
  "Return a gexp that runs 'localedef' from LIBC to build LOCALE."
  (define (maybe-version-directory)
    ;; XXX: For libc prior to 2.22, GuixSD did not store locale data in a
    ;; version-specific sub-directory.  Check whether this is the case.
    ;; TODO: Remove this hack once libc 2.21 is buried.
    (let ((version (package-version libc)))
      (if (version>=? version "2.22")
          (list version "/")
          '())))

  #~(begin
      (format #t "building locale '~a'...~%"
              #$(locale-definition-name locale))


@@ 58,20 72,29 @@
                      "-i" #$(locale-definition-source locale)
                      "-f" #$(locale-definition-charset locale)
                      (string-append #$output "/"
                                     #$(package-version libc) "/"
                                     #$@(maybe-version-directory)
                                     #$(locale-definition-name locale))))))

(define* (locale-directory locales
                           #:key (libc (canonical-package glibc)))
(define* (single-locale-directory locales
                                  #:key (libc (canonical-package glibc)))
  "Return a directory containing all of LOCALES for LIBC compiled.

Because locale data formats are incompatible when switching from one libc to
another, locale data is put in a sub-directory named after the 'version' field
of LIBC."
  (define version
    (package-version libc))

  (define build
    #~(begin
        (mkdir #$output)
        (mkdir (string-append #$output "/" #$(package-version libc)))

        ;; XXX: For libcs < 2.22, locale data is stored in the top-level
        ;; directory.
        ;; TODO: Remove this hack once libc 2.21 is buried.
        #$(if (version>=? version "2.22")
              #~(mkdir (string-append #$output "/" #$version))
              #~(symlink "." (string-append #$output "/" #$version)))

        ;; 'localedef' executes 'gzip' to access compressed locale sources.
        (setenv "PATH" (string-append #$gzip "/bin"))


@@ 80,9 103,38 @@ of LIBC."
         (and #$@(map (cut localedef-command <> #:libc libc)
                      locales)))))

  (gexp->derivation "locale" build
  (gexp->derivation (string-append "locale-" version) build
                    #:local-build? #t))

(define* (locale-directory locales
                           #:key (libcs %default-locale-libcs))
  "Return a locale directory containing all of LOCALES for each libc package
listed in LIBCS.

It is useful to list more than one libc when willing to support
already-installed packages built against a different libc since the locale
data format changes between libc versions."
  (match libcs
    ((libc)
     (single-locale-directory locales #:libc libc))
    ((libcs ..1)
     (mlet %store-monad ((dirs (mapm %store-monad
                                     (lambda (libc)
                                       (single-locale-directory locales
                                                                #:libc libc))
                                     libcs)))
       (gexp->derivation "locale-multiple-versions"
                         #~(begin
                             (use-modules (guix build union))
                             (union-build #$output (list #$@dirs)))
                         #:modules '((guix build union))
                         #:local-build? #t
                         #:substitutable? #f)))))

(define %default-locale-libcs
  ;; The libcs for which we build locales by default.
  (list (canonical-package glibc)))

(define %default-locale-definitions
  ;; Arbitrary set of locales that are built by default.  They are here mostly
  ;; to facilitate first-time use to some people, while others may have to add