~ruther/guix-local

5ce59e0413510098f92e4b824d2a24b97ad2e4c9 — Hilton Chain 1 year, 11 months ago 3ef8c93
build-system: zig: Support Zig package manager.

* guix/build-system/zig.scm (zig-build,zig-cross-build)
[#:install-source?,#:skip-build?]: New arguments.
[#:tests?]: Honor #:skip-build?.
* guix/build/zig-build-system.scm (zig-source-install-path)
(zig-input-install-path,unpack-dependencies): New procedures.
(%standard-phases): Add 'unpack-dependencies.
(build,install): Honor #:skip-build?.
* doc/guix.texi (Build Systems)[zig-build-system]: Update documentation.
* gnu/packages/zig.scm (zig-0.9)[native-search-paths]: Add
GUIX_ZIG_PACKAGE_PATH.
Use search paths defined in (guix search-paths).
(add-build.zig.zon,rename-zig-dependencies): New procedures.
* gnu/packages/ncdu.scm (ncdu)[arguments]: Don't install source.
* gnu/packages/zig-xyz.scm (river,tigerbeetle,zig-zls)[arguments]: Likewise.
M doc/guix.texi => doc/guix.texi +24 -11
@@ 10190,17 10190,30 @@ build system (@command{zig build} command).
Selecting this build system adds @code{zig} to the package inputs, in
addition to the packages of @code{gnu-build-system}.

There is no @code{configure} phase because Zig packages typically do not
need to be configured.  The @code{#:zig-build-flags} parameter is a list of
flags that are passed to the @code{zig} command during the build.  The
@code{#:zig-test-flags} parameter is a list of flags that are passed to the
@code{zig test} command during the @code{check} phase.  The default compiler
package can be overridden with the @code{#:zig} argument.

The optional @code{zig-release-type} parameter declares the type of release.
Possible values are: @code{safe}, @code{fast}, or @code{small}. The default
value is @code{#f}, which causes the release flag to be omitted from the
@code{zig} command.  That results in a @code{debug} build.
This build system by default installs package source to output.  This
behavior can be disabled by setting @code{#:install-source?} parameter
to @code{#f}.

For packages that don't install anything and don't come with a test
suite (likely library packages to be used by other Zig packages), you
can set @code{#:skip-build?} parameter to @code{#t}, which skips
@code{build} and @code{check} phases.

The @code{configure} phase sets up environment for @command{zig build}.
You need to add custom phases after it if you want to invoke
@command{zig}.

The @code{#:zig-build-flags} parameter is a list of flags that are
passed to @command{zig build} in @code{build} phase.  The
@code{#:zig-test-flags} parameter is a list of flags that are passed to
@command{zig build test} in @code{check} phase.  The default compiler
package can be overridden with the @code{#:zig} parameter.

The optional @code{#:zig-release-type} parameter declares the type of
release.  Possible values are: @code{"safe"}, @code{"fast"}, or
@code{"small"}.  The default value is @code{#f}, which causes the
release flag to be omitted from the @code{zig} command and results in a
@code{"debug"} build.
@end defvar

@defvar scons-build-system

M gnu/packages/ncdu.scm => gnu/packages/ncdu.scm +2 -1
@@ 72,7 72,8 @@ ncurses installed.")
                "01g5mpvsm78lkd0yin82gyancrl23npy69qcp3d60vmm72yiwirz"))))
    (build-system zig-build-system)
    (arguments
     (list #:zig zig-0.12))
     (list #:zig zig-0.12
           #:install-source? #f))
    (inputs (list ncurses `(,zstd "lib")))
    (native-inputs (list pkg-config))
    (properties `((tunable? . #t)))))

M gnu/packages/zig-xyz.scm => gnu/packages/zig-xyz.scm +5 -2
@@ 53,6 53,7 @@
    (build-system zig-build-system)
    (arguments
     (list
      #:install-source? #f
      #:phases
      #~(modify-phases %standard-phases
          (add-after 'install 'install-wayland-session


@@ 98,6 99,7 @@ directly from a tty using KMS/DRM.")
    (arguments
     (list
      #:zig zig-0.9
      #:install-source? #f
      #:zig-release-type "safe"))
    (synopsis "Distributed financial accounting database")
    (description "TigerBeetle is a financial accounting database designed for


@@ 122,8 124,9 @@ mission-critical safety and performance for financial services.")
    (build-system zig-build-system)
    (inputs (list zig-0.10 python))
    (arguments
     ;; The tests fail with memory leaks.
     (list #:tests? #f))
     (list #:install-source? #f
           ;; The tests fail with memory leaks.
           #:tests? #f))
    (synopsis "Zig language server")
    (description
     "Zig Language Server is a language server implementing the @acronym{LSP,

M gnu/packages/zig.scm => gnu/packages/zig.scm +52 -11
@@ 24,6 24,7 @@
  #:use-module (guix gexp)
  #:use-module (guix packages)
  #:use-module (guix platform)
  #:use-module (guix search-paths)
  #:use-module (guix utils)
  #:use-module (guix download)
  #:use-module (guix git-download)


@@ 33,7 34,51 @@
  #:use-module (gnu packages compression)
  #:use-module (gnu packages llvm)
  #:use-module (gnu packages llvm-meta)
  #:use-module (gnu packages web))
  #:use-module (gnu packages web)
  #:export (add-build.zig.zon
            rename-zig-dependencies))

(define* (add-build.zig.zon name version dependencies #:optional (paths '("")))
  "Snippet to generate build.zig.zon of DEPENDENCIES for package NAME@VERSION."
  `(let ((port (open-file "build.zig.zon" "w" #:encoding "utf8")))
     (format port "\
.{
    .name = \"~a\",
    .version = \"~a\",
    .paths = .{
~{\
        \"~a\",
~}\
    },
    .dependencies = .{
~{\
        .@\"~a\" = .{
            .url = \"\",
        },
~}\
    },
}~%" ,name ,version (quote ,paths) (quote ,dependencies))
     (close-port port)))

(define* (rename-zig-dependencies mapping #:optional (directories '(".")))
  "Snippet to rename Zig dependencies in build.zig and build.zig.zon."
  `(begin
     (use-modules (ice-9 match)
                  (guix build utils))
     (for-each
      (lambda (directory)
        (for-each
         (match-lambda
           ((old-name . new-name)
            (with-directory-excursion directory
              (substitute* "build.zig"
                (((string-append "([Dd]ependency.\")" old-name) _ prefix)
                 (string-append prefix new-name)))
              (substitute* "build.zig.zon"
                (((format #f "\\.(@\")?~a\"?" old-name))
                 (format #f ".@\"~a\"" new-name))))))
         (quote ,mapping)))
      (quote ,directories))))

(define (zig-source version commit hash)
  (origin


@@ 169,16 214,12 @@
     (list llvm-13
           zig-0.9-glibc-abi-tool))
    (native-search-paths
     (list
      (search-path-specification
       (variable "C_INCLUDE_PATH")
       (files '("include")))
      (search-path-specification
       (variable "CPLUS_INCLUDE_PATH")
       (files '("include/c++" "include")))
      (search-path-specification
       (variable "LIBRARY_PATH")
       (files '("lib" "lib64")))))
     (list $C_INCLUDE_PATH
           $CPLUS_INCLUDE_PATH
           $LIBRARY_PATH
           (search-path-specification
            (variable "GUIX_ZIG_PACKAGE_PATH")
            (files '("src/zig")))))
    (synopsis "General purpose programming language and toolchain")
    (description "Zig is a general-purpose programming language and
toolchain.  Among other features it provides

M guix/build-system/zig.scm => guix/build-system/zig.scm +10 -2
@@ 48,6 48,8 @@
                    source
                    (tests? #t)
                    (test-target #f)
                    (install-source? #t)
                    (skip-build? #f)
                    (zig-build-flags ''())
                    (zig-test-flags ''())
                    (zig-release-type #f)


@@ 68,13 70,15 @@
                     #:source #+source
                     #:system #$system
                     #:test-target #$test-target
                     #:install-source? #$install-source?
                     #:skip-build? #$skip-build?
                     #:zig-build-flags #$zig-build-flags
                     ;; For reproducibility.
                     #:zig-build-target #$(platform-target
                                           (lookup-platform-by-system system))
                     #:zig-test-flags #$zig-test-flags
                     #:zig-release-type #$zig-release-type
                     #:tests? #$tests?
                     #:tests? #$(and tests? (not skip-build?))
                     #:phases #$phases
                     #:outputs #$(outputs->gexp outputs)
                     #:search-paths '#$(sexp->gexp


@@ 98,6 102,8 @@
                          (native-search-paths '())
                          (tests? #t)
                          (test-target #f)
                          (install-source? #t)
                          (skip-build? #f)
                          (zig-build-flags ''())
                          (zig-test-flags ''())
                          (zig-destdir "out")


@@ 141,13 147,15 @@
                     #:native-search-paths '#$(map
                                                search-path-specification->sexp
                                                native-search-paths)
                     #:install-source? #$install-source?
                     #:skip-build? #$skip-build?
                     #:zig-build-flags #$zig-build-flags
                     #:zig-build-target #$target
                     #:zig-test-flags #$zig-test-flags
                     #:zig-release-type #$zig-release-type
                     #:zig-destdir #$zig-destdir
                     #:zig-test-destdir #$zig-test-destdir
                     #:tests? #$tests?
                     #:tests? #$(and tests? (not skip-build?))
                     #:search-paths '#$(sexp->gexp
                                        (map search-path-specification->sexp
                                             search-paths))))))

M guix/build/zig-build-system.scm => guix/build/zig-build-system.scm +104 -22
@@ 24,38 24,113 @@
  #:use-module (ice-9 popen)
  #:use-module (ice-9 rdelim)
  #:use-module (ice-9 ftw)
  #:use-module (ice-9 format)
  #:use-module (ice-9 match)
  #:use-module (rnrs io ports)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:export (%standard-phases
            zig-build))
            zig-build
            zig-source-install-path))

;; Interesting guide here:
;; https://github.com/riverwm/river/blob/master/PACKAGING.md

(define (zig-source-install-path output)
  (string-append output "/src/zig/" (strip-store-file-name output)))

(define (zig-input-install-path input)
  (zig-source-install-path
   (dirname (dirname (dirname (canonicalize-path input))))))

;; Notes on Zig package manager (`build.zig.zon')
;; 1. Dependency definition (name -> URL + hash)
;; - Dependency names are not necessarily consistent across packages.
;; - `zig fetch <url> --name=<name>' fetches <url> to Zig cache, computes its
;; hash, then overwrites dependency <name> with <url> and the computed hash.
;; 2. Dependency lookup
;; Lookup hash in Zig cache, fetch URL when missing.
;; 3. Recursive dependency is possible
;; `build.zig.zon' -> dependency (`build.zig.zon' -> next dependency).
;;
;; With our naming convention and different expectation on package sources,
;; we'll need to change dependency names and hashes for nearly every Zig
;; package.
;; - `rename-zig-dependencies' in `(gnu packages zig)' takes care of names.
;; - `unpack-dependencies' below is for hashes.  Ideally we can parse
;; `build.zig.zon' and only `zig fetch' required dependencies for current
;; package, this way recursive dependencies are resolved by fetching store paths
;; defined in dependency URLs.  However Zig package manager currently doesn't
;; support file URLs[0] so we are instead fetching all dependencies needed for
;; the build process and all of them are added to involved `build.zig.zon' as a
;; side effect.
;; [0]: https://github.com/ziglang/zig/pull/21931
(define (unpack-dependencies . _)
  "Unpack Zig dependencies from GUIX_ZIG_PACKAGE_PATH."
  (define zig-inputs
    (append-map
     (lambda (directory)
       (map (lambda (input-name)
              (cons input-name
                    (in-vicinity directory input-name)))
            (scandir directory (negate (cut member <> '("." ".."))))))
     (or (and=> (getenv "GUIX_ZIG_PACKAGE_PATH")
                (cut string-split <> #\:))
         '())))
  (define (strip-version input)
    (let* ((name+version (string-split input #\-))
           (penult (first (take-right name+version 2)))
           (ultima (last name+version)))
      (string-join
       (drop-right name+version
                   (cond
                    ;; aaa-0.0.0
                    ((= 3 (length (string-split ultima #\.)))
                     1)
                    ;; bbb-0.0.0-0.0000000-output
                    ((and (= 2 (length (string-split penult #\.)))
                          (not (string-contains ultima ".")))
                     3)
                    ;; ccc-0.0.0-output
                    ;; ddd-0.0.0-0.0000000
                    (else 2)))
       "-")))
  (for-each
   (lambda (build.zig.zon)
     (format #t "unpacking dependencies for ~s...~%" build.zig.zon)
     (with-directory-excursion (dirname build.zig.zon)
       (false-if-exception
        (for-each
         (match-lambda
           ((input-name . input-path)
            (let ((call `("zig" "fetch"
                          ,(zig-input-install-path input-path)
                          ,(string-append "--save=" (strip-version input-name)))))
              (format #t "running: ~s~%" call)
              (apply invoke call))))
         (reverse zig-inputs)))))
   (find-files "." "^build\\.zig\\.zon$")))

(define* (build #:key
                zig-build-flags
                zig-build-target
                zig-release-type       ;; "safe", "fast" or "small" empty for a
                                       ;; debug build"
                ;; "safe", "fast" or "small", empty for a "debug" build.
                zig-release-type
                skip-build?
                #:allow-other-keys)
  "Build a given Zig package."

  (setenv "DESTDIR" "out")
  (let ((call `("zig" "build"
                     "--prefix"             ""            ;; Don't add /usr
                     "--prefix-lib-dir"     "lib"
                     "--prefix-exe-dir"     "bin"
                     "--prefix-include-dir" "include"
                     ,(string-append "-Dtarget=" (zig-target zig-build-target))
                     ,@(if zig-release-type
                         (list (string-append "-Drelease-" zig-release-type))
                         '())
                     ,@zig-build-flags)))
  (format #t "running: ~s~%" call)
  (apply invoke call)))
  (when (not skip-build?)
    (setenv "DESTDIR" "out")
    (let ((call `("zig" "build"
                  "--prefix"             ""            ;; Don't add /usr
                  "--prefix-lib-dir"     "lib"
                  "--prefix-exe-dir"     "bin"
                  "--prefix-include-dir" "include"
                  ,(string-append "-Dtarget=" (zig-target zig-build-target))
                  ,@(if zig-release-type
                        (list (string-append "-Drelease-" zig-release-type))
                        '())
                  ,@zig-build-flags)))
      (format #t "running: ~s~%" call)
      (apply invoke call))))

(define* (check #:key tests?
                zig-test-flags


@@ 73,15 148,22 @@
        (setenv "DESTDIR" old-destdir)
        (unsetenv "DESTDIR")))))

(define* (install #:key inputs outputs #:allow-other-keys)
(define* (install #:key outputs install-source? #:allow-other-keys)
  "Install a given Zig package."
  (let ((out (assoc-ref outputs "out")))
    (copy-recursively "out" out)))
  (let* ((out (assoc-ref outputs "out"))
         (source-install-path (zig-source-install-path out)))
    (when (file-exists? "out")
      (copy-recursively "out" out)
      (delete-file-recursively "out"))
    (when install-source?
      (mkdir-p source-install-path)
      (copy-recursively "." source-install-path))))

(define %standard-phases
  (modify-phases gnu:%standard-phases
    (delete 'bootstrap)
    (replace 'configure zig-configure)
    (add-after 'configure 'unpack-dependencies unpack-dependencies)
    (replace 'build build)
    (replace 'check check)
    (replace 'install install)))