summary refs log tree commit diff
path: root/doc/build.scm
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2019-07-07 15:00:43 +0200
committerLudovic Courtès <ludo@gnu.org>2019-07-07 15:48:17 +0200
commitccadafdcefee012c261513e9d8663a22704bc496 (patch)
tree83c85566290929261254dfb9e7bc915b507f5c3a /doc/build.scm
parentaad65962944098736f0e357683ed12d554cf5e8e (diff)
downloadguix-ccadafdcefee012c261513e9d8663a22704bc496.tar.gz
build: Add 'doc/build.scm' to build on-line copies of the manual.
* doc/build.scm: New file.
* Makefile.am (EXTRA_DIST): Add it.
Diffstat (limited to 'doc/build.scm')
-rw-r--r--doc/build.scm563
1 files changed, 563 insertions, 0 deletions
diff --git a/doc/build.scm b/doc/build.scm
new file mode 100644
index 0000000000..e628a91048
--- /dev/null
+++ b/doc/build.scm
@@ -0,0 +1,563 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2019 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+
+;; This file contains machinery to build HTML and PDF copies of the manual
+;; that can be readily published on the web site.  To do that, run:
+;;
+;;  guix build -f build.scm
+;;
+;; The result is a directory hierarchy that can be used as the manual/
+;; sub-directory of the web site.
+
+(use-modules (guix)
+             (guix gexp)
+             (guix git)
+             (guix git-download)
+             (git)
+             (gnu packages base)
+             (gnu packages gawk)
+             (gnu packages gettext)
+             (gnu packages guile)
+             (gnu packages texinfo)
+             (gnu packages tex)
+             (srfi srfi-19)
+             (srfi srfi-71))
+
+(define file-append*
+  (@@ (guix self) file-append*))
+
+(define translated-texi-manuals
+  (@@ (guix self) translate-texi-manuals))
+
+(define info-manual
+  (@@ (guix self) info-manual))
+
+(define %languages
+  '("de" "en" "es" "fr" "ru" "zh_CN"))
+
+(define (texinfo-manual-images source)
+  "Return a directory containing all the images used by the user manual, taken
+from SOURCE, the root of the source tree."
+  (define graphviz
+    (module-ref (resolve-interface '(gnu packages graphviz))
+                'graphviz))
+
+  (define images
+    (file-append* source "doc/images"))
+
+  (define build
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils)
+                       (srfi srfi-26))
+
+          (define (dot->image dot-file format)
+            (invoke #+(file-append graphviz "/bin/dot")
+                    "-T" format "-Gratio=.9" "-Gnodesep=.005"
+                    "-Granksep=.00005" "-Nfontsize=9"
+                    "-Nheight=.1" "-Nwidth=.1"
+                    "-o" (string-append #$output "/"
+                                        (basename dot-file ".dot")
+                                        "." format)
+                    dot-file))
+
+          ;; Build graphs.
+          (mkdir-p #$output)
+          (for-each (lambda (dot-file)
+                      (for-each (cut dot->image dot-file <>)
+                                '("png" "pdf")))
+                    (find-files #$images "\\.dot$"))
+
+          ;; Copy other PNGs.
+          (for-each (lambda (png-file)
+                      (install-file png-file #$output))
+                    (find-files #$images "\\.png$")))))
+
+  (computed-file "texinfo-manual-images" build))
+
+(define* (texinfo-manual-source source #:key
+                                (version "0.0")
+                                (languages %languages)
+                                (date 1))
+  "Gather all the source files of the Texinfo manuals from SOURCE--.texi file
+as well as images, OS examples, and translations."
+  (define documentation
+    (file-append* source "doc"))
+
+  (define examples
+    (file-append* source "gnu/system/examples"))
+
+  (define build
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils)
+                       (srfi srfi-19))
+
+          (define (make-version-texi language)
+            ;; Create the 'version.texi' file for LANGUAGE.
+            (let ((file (if (string=? language "en")
+                            "version.texi"
+                            (string-append "version-" language ".texi"))))
+              (call-with-output-file (string-append #$output "/" file)
+                (lambda (port)
+                  (let* ((version #$version)
+                         (time    (make-time time-utc 0 #$date))
+                         (date    (time-utc->date time)))
+                    (format port "
+@set UPDATED ~a
+@set UPDATED-MONTH ~a
+@set EDITION ~a
+@set VERSION ~a\n"
+                            (date->string date "~e ~B ~Y")
+                            (date->string date "~B ~Y")
+                            version version))))))
+
+          (install-file #$(file-append* documentation "/htmlxref.cnf")
+                        #$output)
+
+          (for-each (lambda (texi)
+                      (install-file texi #$output))
+                    (append (find-files #$documentation "\\.(texi|scm)$")
+                            (find-files #$(translated-texi-manuals source)
+                                        "\\.texi$")))
+
+          ;; Create 'version.texi'.
+          (for-each make-version-texi '#$languages)
+
+          ;; Copy configuration templates that the manual includes.
+          (for-each (lambda (template)
+                      (copy-file template
+                                 (string-append
+                                  #$output "/os-config-"
+                                  (basename template ".tmpl")
+                                  ".texi")))
+                    (find-files #$examples "\\.tmpl$"))
+
+          (symlink #$(texinfo-manual-images source)
+                   (string-append #$output "/images")))))
+
+  (computed-file "texinfo-manual-source" build))
+
+(define %web-site-url
+  ;; URL of the web site home page.
+  (or (getenv "GUIX_WEB_SITE_URL")
+      "/software/guix/"))
+
+(define %makeinfo-html-options
+  ;; Options passed to 'makeinfo --html'.
+  '("--css-ref=https://www.gnu.org/software/gnulib/manual.css"))
+
+(define* (html-manual source #:key (languages %languages)
+                      (version "0.0")
+                      (manual "guix")
+                      (date 1)
+                      (options %makeinfo-html-options))
+  "Return the HTML manuals built from SOURCE for all LANGUAGES, with the given
+makeinfo OPTIONS."
+  (define manual-source
+    (texinfo-manual-source source
+                           #:version version
+                           #:languages languages
+                           #:date date))
+
+  (define build
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils)
+                       (ice-9 match))
+
+          (define (normalize language)
+            ;; Normalize LANGUAGE.  For instance, "zh_CN" become "zh-cn".
+            (string-map (match-lambda
+                          (#\_ #\-)
+                          (chr chr))
+                        (string-downcase language)))
+
+          ;; Install a UTF-8 locale so that 'makeinfo' is at ease.
+          (setenv "GUIX_LOCPATH"
+                  #+(file-append glibc-utf8-locales "/lib/locale"))
+          (setenv "LC_ALL" "en_US.utf8")
+
+          (setvbuf (current-output-port) 'line)
+          (setvbuf (current-error-port) 'line)
+
+          (for-each (lambda (language)
+                      (let ((opts `("--html"
+                                    "-c" ,(string-append "TOP_NODE_UP_URL=/manual/"
+                                                         language)
+                                    #$@options
+                                    ,(if (string=? language "en")
+                                         (string-append #$manual-source "/"
+                                                        #$manual ".texi")
+                                         (string-append #$manual-source "/"
+                                                        #$manual "." language ".texi")))))
+                        (format #t "building HTML manual for language '~a'...~%"
+                                language)
+                        (mkdir-p (string-append #$output "/"
+                                                (normalize language)))
+                        (setenv "LANGUAGE" language)
+                        (apply invoke #$(file-append texinfo "/bin/makeinfo")
+                               "-o" (string-append #$output "/"
+                                                   (normalize language)
+                                                   "/html_node")
+                               opts)
+                        (apply invoke #$(file-append texinfo "/bin/makeinfo")
+                               "--no-split"
+                               "-o"
+                               (string-append #$output "/"
+                                              (normalize language)
+                                              "/" #$manual
+                                              (if (string=? language "en")
+                                                  ""
+                                                  (string-append "." language))
+                                              ".html")
+                               opts)))
+                    '#$languages))))
+
+  (computed-file (string-append manual "-html-manual") build))
+
+(define* (pdf-manual source #:key (languages %languages)
+                     (version "0.0")
+                     (manual "guix")
+                     (date 1)
+                     (options '()))
+  "Return the HTML manuals built from SOURCE for all LANGUAGES, with the given
+makeinfo OPTIONS."
+  (define manual-source
+    (texinfo-manual-source source
+                           #:version version
+                           #:languages languages
+                           #:date date))
+
+  ;; FIXME: This union works, except for the table of contents of non-English
+  ;; manuals, which contains escape sequences like "^^ca^^fe" instead of
+  ;; accented letters.
+  ;;
+  ;; (define texlive
+  ;;   (texlive-union (list texlive-tex-texinfo
+  ;;                        texlive-generic-epsf
+  ;;                        texlive-fonts-ec)))
+
+  (define build
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils)
+                       (srfi srfi-34)
+                       (ice-9 match))
+
+          (define (normalize language)            ;XXX: deduplicate
+            ;; Normalize LANGUAGE.  For instance, "zh_CN" becomes "zh-cn".
+            (string-map (match-lambda
+                          (#\_ #\-)
+                          (chr chr))
+                        (string-downcase language)))
+
+          ;; Install a UTF-8 locale so that 'makeinfo' is at ease.
+          (setenv "GUIX_LOCPATH"
+                  #+(file-append glibc-utf8-locales "/lib/locale"))
+          (setenv "LC_ALL" "en_US.utf8")
+          (setenv "PATH"
+                  (string-append #+(file-append texlive "/bin") ":"
+                                 #+(file-append texinfo "/bin") ":"
+
+                                 ;; Below are command-line tools needed by
+                                 ;; 'texi2dvi' and friends.
+                                 #+(file-append sed "/bin") ":"
+                                 #+(file-append grep "/bin") ":"
+                                 #+(file-append coreutils "/bin") ":"
+                                 #+(file-append gawk "/bin") ":"
+                                 #+(file-append tar "/bin") ":"
+                                 #+(file-append diffutils "/bin")))
+
+          (setvbuf (current-output-port) 'line)
+          (setvbuf (current-error-port) 'line)
+
+          (setenv "HOME" (getcwd))                ;for kpathsea/mktextfm
+
+          ;; 'SOURCE_DATE_EPOCH' is honored by pdftex.
+          (setenv "SOURCE_DATE_EPOCH" "1")
+
+          (for-each (lambda (language)
+                      (let ((opts `("--pdf"
+                                    "-I" "."
+                                    #$@options
+                                    ,(if (string=? language "en")
+                                         (string-append #$manual-source "/"
+                                                        #$manual ".texi")
+                                         (string-append #$manual-source "/"
+                                                        #$manual "." language ".texi")))))
+                        (format #t "building PDF manual for language '~a'...~%"
+                                language)
+                        (mkdir-p (string-append #$output "/"
+                                                (normalize language)))
+                        (setenv "LANGUAGE" language)
+
+
+                        ;; FIXME: Unfortunately building PDFs for non-Latin
+                        ;; alphabets doesn't work:
+                        ;; <https://lists.gnu.org/archive/html/help-texinfo/2012-01/msg00014.html>.
+                        (guard (c ((invoke-error? c)
+                                   (format (current-error-port)
+                                           "~%~%Failed to produce \
+PDF for language '~a'!~%~%"
+                                           language)))
+                         (apply invoke #$(file-append texinfo "/bin/makeinfo")
+                                "--pdf" "-o"
+                                (string-append #$output "/"
+                                               (normalize language)
+                                               "/" #$manual
+                                               (if (string=? language "en")
+                                                   ""
+                                                   (string-append "."
+                                                                  language))
+                                               ".pdf")
+                                opts))))
+                    '#$languages))))
+
+  (computed-file (string-append manual "-pdf-manual") build))
+
+(define (guix-manual-text-domain source languages)
+  "Return the PO files for LANGUAGES of the 'guix-manual' text domain taken
+from SOURCE."
+  (define po-directory
+    (file-append* source "/po/doc"))
+
+  (define build
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils))
+
+          (mkdir-p #$output)
+          (for-each (lambda (language)
+                      (define directory
+                        (string-append #$output "/" language
+                                       "/LC_MESSAGES"))
+
+                      (mkdir-p directory)
+                      (invoke #+(file-append gnu-gettext "/bin/msgfmt")
+                              "-c" "-o"
+                              (string-append directory "/guix-manual.mo")
+                              (string-append #$po-directory "/guix-manual."
+                                             language ".po")))
+                    '#$(delete "en" languages)))))
+
+  (computed-file "guix-manual-po" build))
+
+(define* (html-manual-indexes source
+                              #:key (languages %languages)
+                              (version "0.0")
+                              (manual "guix")
+                              (date 1))
+  (define build
+    (with-imported-modules '((guix build utils))
+      #~(begin
+          (use-modules (guix build utils)
+                       (ice-9 match)
+                       (ice-9 popen)
+                       (sxml simple)
+                       (srfi srfi-19))
+
+          (define (normalize language)            ;XXX: deduplicate
+            ;; Normalize LANGUAGE.  For instance, "zh_CN" become "zh-cn".
+            (string-map (match-lambda
+                          (#\_ #\-)
+                          (chr chr))
+                        (string-downcase language)))
+
+          (define-syntax-rule (with-language language exp ...)
+            (let ((lang (getenv "LANGUAGE")))
+              (dynamic-wind
+                (lambda ()
+                  (setenv "LANGUAGE" language)
+                  (setlocale LC_MESSAGES))
+                (lambda () exp ...)
+                (lambda ()
+                  (if lang
+                      (setenv "LANGUAGE" lang)
+                      (unsetenv "LANGUAGE"))
+                  (setlocale LC_MESSAGES)))))
+
+          ;; (put 'with-language 'scheme-indent-function 1)
+          (define* (translate str language
+                              #:key (domain "guix-manual"))
+            (define exp
+              `(begin
+                 (bindtextdomain "guix-manual"
+                                 #+(guix-manual-text-domain
+                                    source
+                                    languages))
+                 (write (gettext ,str "guix-manual"))))
+
+            (with-language language
+              ;; Since the 'gettext' function caches msgid translations,
+              ;; regardless of $LANGUAGE, we have to spawn a new process each
+              ;; time we want to translate to a different language.  Bah!
+              (let* ((pipe (open-pipe* OPEN_READ
+                                       #+(file-append guile-2.2
+                                                      "/bin/guile")
+                                       "-c" (object->string exp)))
+                     (str  (read pipe)))
+                (close-pipe pipe)
+                str)))
+
+          (define (seconds->string seconds language)
+            (let* ((time (make-time time-utc 0 seconds))
+                   (date (time-utc->date time)))
+              (with-language language (date->string date "~e ~B ~Y"))))
+
+          (define (guix-url path)
+            (string-append #$%web-site-url path))
+
+          (define (sxml-index language)
+            (define title
+              (translate "GNU Guix Reference Manual" language))
+
+            ;; FIXME: Avoid duplicating styling info from guix-artwork.git.
+            `(html (@ (lang ,language))
+                   (head
+                    (title ,(string-append title " — GNU Guix"))
+                    (meta (@ (charset "UTF-8")))
+                    (meta (@ (name "viewport") (content "width=device-width, initial-scale=1.0")))
+                    ;; Menu prefetch.
+                    (link (@ (rel "prefetch") (href ,(guix-url "menu/index.html"))))
+                    ;; Base CSS.
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/elements.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/common.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/messages.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/navbar.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/breadcrumbs.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/buttons.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/footer.css"))))
+
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/page.css"))))
+                    (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/post.css")))))
+                   (body
+                    (header (@ (class "navbar"))
+                            (h1 (a (@ (class "branding")
+                                      (href #$%web-site-url)))
+                                (span (@ (class "a11y-offset"))
+                                      "Guix"))
+                            (nav (@ (class "menu"))))
+                    (nav (@ (class "breadcrumbs"))
+                         (a (@ (class "crumb")
+                               (href #$%web-site-url))
+                            "Home"))
+                    (main
+                     (article
+                      (@ (class "page centered-block limit-width"))
+                      (h2 ,title)
+                      (p (@ (class "post-metadata centered-text"))
+                         #$version " — "
+                         ,(seconds->string #$date language))
+
+                      (div
+                       (ul
+                        (li (a (@ (href "html_node"))
+                               "HTML, with one page per node"))
+                        (li (a (@ (href
+                                   ,(string-append
+                                     #$manual
+                                     (if (string=? language
+                                                   "en")
+                                         ""
+                                         (string-append "."
+                                                        language))
+                                     ".html")))
+                               "HTML, entirely on one page"))
+                        ,@(if (member language '("ru" "zh_CN"))
+                              '()
+                              `((li (a (@ (href ,(string-append
+                                                  #$manual
+                                                  (if (string=? language "en")
+                                                      ""
+                                                      (string-append "."
+                                                                     language))
+                                                  ".pdf"))))
+                                    "PDF")))))))
+                    (footer))))
+
+          (define (write-index language file)
+            (call-with-output-file file
+              (lambda (port)
+                (display "<!DOCTYPE html>\n" port)
+                (sxml->xml (sxml-index language) port))))
+
+          (setenv "GUIX_LOCPATH"
+                  #+(file-append glibc-utf8-locales "/lib/locale"))
+          (setenv "LC_ALL" "en_US.utf8")
+          (setlocale LC_ALL "en_US.utf8")
+
+          (bindtextdomain "guix-manual"
+                          #+(guix-manual-text-domain source languages))
+
+          (for-each (lambda (language)
+                      (define directory
+                        (string-append #$output "/"
+                                       (normalize language)))
+
+                      (mkdir-p directory)
+                      (write-index language
+                                   (string-append directory
+                                                  "/index.html")))
+                    '#$languages))))
+
+  (computed-file "html-indexes" build))
+
+(define* (pdf+html-manual source
+                          #:key (languages %languages)
+                          (version "0.0")
+                          (date (time-second (current-time time-utc)))
+                          (manual "guix"))
+  "Return the union of the HTML and PDF manuals, as well as the indexes."
+  (directory-union (string-append manual "-manual")
+                   (map (lambda (proc)
+                          (proc source
+                                #:date date
+                                #:languages languages
+                                #:version version
+                                #:manual manual))
+                        (list html-manual-indexes
+                              html-manual pdf-manual))
+                   #:copy? #t))
+
+(define (latest-commit+date directory)
+  "Return two values: the last commit ID (a hex string) for DIRECTORY, and its
+commit date (an integer)."
+  (let* ((repository (repository-open directory))
+         (head       (repository-head repository))
+         (oid        (reference-target head))
+         (commit     (commit-lookup repository oid)))
+    ;; TODO: Use (git describe) when it's widely available.
+    (values (oid->string oid) (commit-time commit))))
+
+
+(let* ((root (canonicalize-path
+              (string-append (current-source-directory) "/..")))
+       (commit date (latest-commit+date root)))
+  (format (current-error-port)
+          "building manual from work tree around commit ~a, ~a~%"
+          commit
+          (let* ((time (make-time time-utc 0 date))
+                 (date (time-utc->date time)))
+            (date->string date "~e ~B ~Y")))
+  (pdf+html-manual (local-file root "guix" #:recursive? #t
+                               #:select? (git-predicate root))
+                   #:version (or (getenv "GUIX_MANUAL_VERSION")
+                                 (string-take commit 7))
+                   #:date date))