diff mbox series

[bug#66801,v3,01/14] build-system: Add mix-build-system.

Message ID ce8c173ef0b340b6a500bacd5821064a1bffe4fc.1700208022.git.phfrohring@deeplinks.com
State New
Headers show
Series [bug#66801,v3,01/14] build-system: Add mix-build-system. | expand

Commit Message

Pierre-Henry Fröhring Nov. 17, 2023, 8:03 a.m. UTC
* guix/build-system/mix.scm,
* guix/build/mix-build-system.scm: New modules.

Change-Id: I2cbf6c963a530e73420da0eb17ffaf92827451bf
---
 gnu/packages/elixir.scm         |  62 ++++++++++-
 guix/build-system/mix.scm       | 187 ++++++++++++++++++++++++++++++++
 guix/build/mix-build-system.scm | 171 +++++++++++++++++++++++++++++
 3 files changed, 417 insertions(+), 3 deletions(-)
 create mode 100644 guix/build-system/mix.scm
 create mode 100644 guix/build/mix-build-system.scm


base-commit: a0d337e79c87d7c38c79d0291974f490cb137a52

Comments

Liliana Marie Prikler Nov. 17, 2023, 7:24 p.m. UTC | #1
Am Freitag, dem 17.11.2023 um 09:03 +0100 schrieb Pierre-Henry
Fröhring:
> * guix/build-system/mix.scm,
> * guix/build/mix-build-system.scm: New modules.
Avoid spanning a change across multiple files.  Use 
file: Change.
file2: Likewise.

> Change-Id: I2cbf6c963a530e73420da0eb17ffaf92827451bf
> ---
>  gnu/packages/elixir.scm         |  62 ++++++++++-
>  guix/build-system/mix.scm       | 187
> ++++++++++++++++++++++++++++++++
>  guix/build/mix-build-system.scm | 171 +++++++++++++++++++++++++++++
You committed two changes at once here.  Split them.

>  3 files changed, 417 insertions(+), 3 deletions(-)
>  create mode 100644 guix/build-system/mix.scm
>  create mode 100644 guix/build/mix-build-system.scm
> 
> diff --git a/gnu/packages/elixir.scm b/gnu/packages/elixir.scm
> index 724b4251..6f779afb 100644
> --- a/gnu/packages/elixir.scm
> +++ b/gnu/packages/elixir.scm
> @@ -27,6 +27,7 @@ (define-module (gnu packages elixir)
>    #:use-module ((guix licenses) #:prefix license:)
>    #:use-module (guix build-system gnu)
>    #:use-module (guix gexp)
> +  #:use-module (guix utils)
>    #:use-module (guix git-download)
>    #:use-module (guix packages)
>    #:use-module (gnu packages)
> @@ -96,9 +97,21 @@ (define-public elixir
>              (lambda* (#:key inputs #:allow-other-keys)
>                ;; Some tests require access to a home directory.
>                (setenv "HOME" "/tmp")))
> -          (delete 'configure))))
> -    (inputs
> -     (list erlang git))
> +          (delete 'configure)
> +          (add-after 'install 'wrap-programs
> +            (lambda* (#:key inputs outputs #:allow-other-keys)
> +              (let* ((out (assoc-ref outputs "out"))
> +                     (programs '("elixir" "elixirc" "iex" "mix")))
> +                (for-each (lambda (program)
> +                            (wrap-program (string-append out "/bin/"
> program)
> +                              '("ERL_LIBS" prefix
> ("${GUIX_ERL_LIBS}"))))
> +                          programs)))))))
> +    (inputs (list erlang git))
> +    (native-search-paths
> +     (list (search-path-specification
> +            (variable "GUIX_ERL_LIBS")
> +            (files (list "lib/erlang/lib"
> +                         (string-append "lib/elixir/" (version-
> major+minor version)))))))
I suppose ERL is for erlang?  What do we use for elixir then?
>      (home-page "https://elixir-lang.org/")
>      (synopsis "Elixir programming language")
>      (description "Elixir is a dynamic, functional language used to
> build
> @@ -106,3 +119,46 @@ (define-public elixir
>  for running low-latency, distributed and fault-tolerant systems,
> while also
>  being successfully used in web development and the embedded software
> domain.")
>      (license license:asl2.0)))
> +
> +(define-public elixir-hex
> +  (package
> +    (name "elixir-hex")
> +    (version "2.0.5")
> +    (source
> +     (origin
> +       (method git-fetch)
> +       (uri (git-reference
> +             (url "https://github.com/hexpm/hex.git")
> +             (commit (string-append "v" version))))
> +       (file-name (git-file-name name version))
> +       (sha256
> +        (base32
> +         "1kvczwvij58kgkhak68004ap81pl26600bczg21mymy2sypkgxmj"))))
> +    ;; The mix-build-system assumes that Hex exists.
> +    ;; We build Hex using the gnu-build-system.
> +    ;; Other Elixir packages use the mix-build-system.
> +    (build-system gnu-build-system)
> +    (inputs (list elixir))
> +    (arguments
> +     (list
> +      #:phases
> +      #~(modify-phases %standard-phases
> +          (delete 'bootstrap)
> +          (delete 'configure)
> +          (replace 'build
> +            (lambda* (#:key inputs #:allow-other-keys)
> +              (setenv "MIX_ENV" "prod")
> +              (invoke "mix" "compile")))
> +          (delete 'check)
> +          (replace 'install
> +            (lambda* (#:key inputs outputs #:allow-other-keys)
> +              (define X.Y #$(version-major+minor (package-version
> elixir)))
> +              (define out (string-append (assoc-ref outputs "out")
> "/lib/elixir/" X.Y "/hex"))
> +              (mkdir-p out)
> +              (copy-recursively "_build/prod/lib/hex" out))))))
> +    (synopsis "Package manager for the Erlang VM")
> +    (description
> +     "This project provides tasks that integrate with Mix, Elixir's
> build
> +tool.")
> +    (home-page "https://hexdocs.pm/makeup_elixir/")
> +    (license license:bsd-2)))
> diff --git a/guix/build-system/mix.scm b/guix/build-system/mix.scm
> new file mode 100644
> index 00000000..7ddedea9
> --- /dev/null
> +++ b/guix/build-system/mix.scm
> @@ -0,0 +1,187 @@
> +;;; GNU Guix --- Functional package management for GNU
> +;;; Copyright © 2023 Pierre-Henry Fröhring
> <phfrohring@deeplinks.com>
> +;;;
> +;;; 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/>.
> +
> +;; Commentary:
> +;;
> +;; Standard build procedure for Elixir packages using 'mix'.  This
> is
> +;; implemented as an extension of 'gnu-build-system'.
> +;;
> +;; Code:
> +
> +(define-module (guix build-system mix)
> +  #:use-module (guix build mix-build-system)
> +  #:use-module (guix build-system gnu)
> +  #:use-module (guix build-system)
> +  #:use-module (guix gexp)
> +  #:use-module (guix monads)
> +  #:use-module (guix packages)
> +  #:use-module (guix search-paths)
> +  #:use-module (guix store)
> +  #:use-module (guix utils)
> +  #:use-module (ice-9 match)
> +  #:use-module (srfi srfi-1)
> +  #:use-module (srfi srfi-26)
> +  #:export (mix-build-system hexpm-uri))
> +
> +;; Lazily resolve the bindings to avoid circular dependencies.
> +(define (default-glibc-utf8-locales)
> +  (let* ((base (resolve-interface '(gnu packages base))))
> +    (module-ref base 'glibc-utf8-locales)))
> +
> +(define (default-elixir-hex)
> +  (let ((elixir (resolve-interface '(gnu packages elixir))))
> +    (module-ref elixir 'elixir-hex)))
> +
> +(define (default-rebar3)
> +  (let ((erlang (resolve-interface '(gnu packages erlang))))
> +    (module-ref erlang 'rebar3)))
> +
> +(define (default-elixir)
> +  "Return the default Elixir package."
> +  (let ((elixir (resolve-interface '(gnu packages elixir))))
> +    (module-ref elixir 'elixir)))
> +
> +(define* (strip-prefix name #:optional (prefix "elixir-"))
> +  "Return NAME without the prefix PREFIX."
> +  (if (string-prefix? prefix name)
> +      (string-drop name (string-length prefix))
> +      name))
> +
> +(define (hexpm-uri name version)
> +  "Return the URI where to fetch the sources of a Hex package NAME
> at VERSION.
> +NAME is the name of the package which should look like: elixir-pkg-
> name-X.Y.Z
> +See: https://github.com/hexpm/specifications/blob/main/endpoints.md"
> +  ((compose
> +    (cut string-append "https://repo.hex.pm/tarballs/" <> "-"
> version ".tar")
> +    (cut string-replace-substring <> "-" "_")
> +    strip-prefix)
> +   name))
> +
> +;; A number of environment variables specific to the Mix build
> system are
> +;; reflected here.  They are documented at
> +;;
> https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables. 
> Other
> +;; parameters located in mix.exs are defined at
> +;;
> https://hexdocs.pm/mix/1.15.7/Mix.Project.html#module-configuration
> +(define* (mix-build name
> +                    inputs
> +                    #:key
> +                    source
> +                    (tests? #t)
> +                    (mix-path #f) ;See MIX_PATH.
> +                    (mix-exs "mix.exs") ;See MIX_EXS.
> +                    (build-per-environment #t) ;See
> :build_per_environment.
> +                    (phases '%standard-phases)
> +                    (outputs '("out"))
> +                    (search-paths '())
> +                    (system (%current-system))
> +                    (guile #f)
> +                    (imported-modules `((guix build mix-build-
> system)
> +                                        ,@%gnu-build-system-
> modules))
> +                    (modules '((guix build mix-build-system)
> +                               (guix build utils))))
> +  "Build SOURCE using Elixir, and with INPUTS."
> +
> +  ;; Check the documentation of :build_per_environment here:
> +  ;;
> https://hexdocs.pm/mix/1.15.7/Mix.Project.html#module-configuration A
> nd
> +  ;; "Environments" here:
> +  ;; https://hexdocs.pm/mix/1.15.7/Mix.html#module-environments
> +  (define mix-environments
> +    (if build-per-environment
> +        `("prod" ,@(if tests? '("test") '()))
> +        '("shared")))
> +
> +  (define builder
> +    (with-imported-modules imported-modules
> +      #~(begin
> +
> +          (use-modules #$@(sexp->gexp modules))
> +
> +          #$(with-build-variables inputs outputs
> +              #~(mix-build #:name #$name
> +                           #:source #+source
> +                           #:system #$system
> +                           #:tests? #$tests?
> +                           #:mix-path #$mix-path
> +                           #:mix-exs #$mix-exs
> +                           #:mix-environments '#$mix-environments
> +                           #:build-per-environment #$build-per-
> environment
> +                           #:phases #$(if (pair? phases)
> +                                          (sexp->gexp phases)
> +                                          phases)
> +                           #:outputs %outputs
> +                           #:search-paths '#$(sexp->gexp
> +                                              (map
> +                                               search-path-
> specification->sexp
> +                                               search-paths))
> +                           #:inputs
> +                           %build-inputs)))))
> +
> +  (mlet %store-monad ((guile (package->derivation (or guile
> (default-guile))
> +                                                  system
> +                                                  #:graft? #f)))
> +    (gexp->derivation name
> +                      builder
> +                      #:system system
> +                      #:graft? #f       ;consistent with 'gnu-build'
> +                      #:target #f
> +                      #:guile-for-build guile)))
> +
> +(define* (lower name
> +                #:key
> +                (elixir (default-elixir))
> +                (elixir-hex (default-elixir-hex))
> +                (glibc-utf8-locales (default-glibc-utf8-locales))
> +                (inputs '())
> +                (native-inputs '())
> +                (propagated-inputs '())
> +                (rebar3 (default-rebar3))
> +                (tests? #t)
> +                outputs
> +                source
> +                system
> +                target
> +                #:allow-other-keys #:rest arguments)
> +  "Return a bag for NAME."
> +  (let ((private-keywords
> +         '(#:inputs #:native-inputs
> +           #:outputs #:system #:target
> +           #:elixir #:elixir-hex #:glibc-utf8-locales
> +           #:rebar3 #:erlang))
> +        (build-inputs
> +         `(,@(standard-packages)
> +           ("glibc-utf8-locales" ,glibc-utf8-locales)
> +           ("erlang" ,(lookup-package-input elixir "erlang"))
> +           ("rebar3" ,rebar3)
> +           ("elixir" ,elixir)
> +           ("elixir-hex" ,elixir-hex)
> +           ,@inputs
> +           ,@native-inputs)))
> +  (bag (name name)
> +       (system system)
> +       (build-inputs build-inputs)
> +       (host-inputs (if target inputs '()))
> +       (outputs outputs)
> +       (build mix-build)
> +       (arguments (strip-keyword-arguments private-keywords
> arguments)))))
> +
> +(define mix-build-system
> +  (build-system (name 'mix)
> +                (description "The standard Mix build system")
> +                (lower lower)))
> +
> +;;; mix.scm ends here
> diff --git a/guix/build/mix-build-system.scm b/guix/build/mix-build-
> system.scm
> new file mode 100644
> index 00000000..ca649582
> --- /dev/null
> +++ b/guix/build/mix-build-system.scm
> @@ -0,0 +1,171 @@
> +;;; GNU Guix --- Functional package management for GNU
> +;;; Copyright © 2023 Pierre-Henry Fröhring
> <phfrohring@deeplinks.com>
> +;;;
> +;;; 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/>.
> +
> +;; Commentary:
> +;;
> +;; Code:
> +
> +(define-module (guix build mix-build-system)
> +  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
> +  #:use-module (guix build utils)
> +  #:use-module (ice-9 ftw)
> +  #:use-module (ice-9 match)
> +  #:use-module (ice-9 regex)
> +  #:use-module (ice-9 string-fun)
> +  #:use-module (srfi srfi-1)
> +  #:use-module (srfi srfi-26)
> +  #:use-module (srfi srfi-71)
> +  #:export (mix-build
> +            %standard-phases))
> +
> +;; The Elixir version is constant as soon as it is computable from
> the current
> +;; execution. It is a X.Y string where X and Y are respectively the
> major and
> +;; minor version number of the Elixir used in the build.
> +(define %elixir-version (make-parameter "X.Y"))
> +
> +(define* (elixir-libdir path #:optional (version (%elixir-version)))
> +  "Return the path where all libraries for a specified ELIXIR
> version are installed."
> +  (string-append path "/lib/elixir/" version))
> +
> +(define (erlang-libdir path)
> +  "Return the path of the directory where libraries of an Erlang
> package are
> +installed in the store."
> +  (string-append path "/lib/erlang/lib"))
> +
> +(define* (strip-prefix name #:optional (prefix "elixir-"))
> +  "Return NAME without the prefix PREFIX."
> +  (if (string-prefix? prefix name)
> +      (string-drop name (string-length prefix))
> +      name))
> +
> +(define (mix-build-dir mix-env)
> +  "Return the directory where build artifacts are to be installed
> according to
> +en environment MIX-ENV in the current directory."
> +  (string-append "_build/" mix-env "/lib"))
> +
> +(define (elixir-version inputs)
> +  "Return an X.Y string where X and Y are respectively the major and
> minor version number of ELIXIR.
> +Example: /gnu/store/…-elixir-1.14.0 → 1.14"
> +  (let* ((_ version (package-name->name+version
> +                     (strip-store-file-name (assoc-ref inputs
> "elixir"))))
> +         (components  (string-split version #\.))
> +         (major+minor (take components 2)))
> +    (string-join major+minor ".")))
> +
> +(define* (unpack #:key source mix-path #:allow-other-keys)
> +  "Unpack SOURCE in the working directory, and change directory
> within the
> +source.  When SOURCE is a directory, copy it in a sub-directory of
> the current
> +working directory."
> +  (let ((gnu-unpack (assoc-ref gnu:%standard-phases 'unpack)))
> +    (gnu-unpack #:source source)
> +    (when (file-exists? "contents.tar.gz")
> +      (invoke "tar" "xvf" "contents.tar.gz"))))
> +
> +(define (list-directories dir)
> +  "List absolute paths of directories directly under the directory
> DIR."
> +  (map (cut string-append dir "/" <>)
> +       (scandir dir (lambda (filename)
> +                      (and (not (member filename '("." "..")))
> +                           (directory-exists? (string-append dir "/"
> filename)))))))
> +
> +(define* (set-mix-env #:key inputs mix-path mix-exs #:allow-other-
> keys)
> +  "Set environment variables.
> +See:
> https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables"
> +  (setenv "MIX_HOME" (getcwd))
> +  (setenv "MIX_ARCHIVES" "archives")
> +  (setenv "MIX_BUILD_ROOT" "_build")
> +  (setenv "MIX_DEPS_PATH" "deps")
> +  (setenv "MIX_PATH" (or mix-path ""))
> +  (setenv "MIX_REBAR3" (string-append (assoc-ref inputs "rebar3")
> "/bin/rebar3"))
> +  (setenv "MIX_EXS" mix-exs)
> +  (%elixir-version (elixir-version inputs)))
%elixir-version is not an environment variable.  You should set this up
separately or at the very least add a big fat comment explaining what
you're doing here :)
> +
> +(define* (install-hex #:key name inputs outputs #:allow-other-keys)
> +  "Install Hex."
> +  (let ((hex-archive-path (string-append (getenv "MIX_ARCHIVES")
> "/hex")))
> +    (mkdir-p hex-archive-path)
> +    (symlink (car (list-directories (elixir-libdir (assoc-ref inputs
> "elixir-hex"))))
> +             (string-append hex-archive-path "/hex"))))
Is this still needed?

> +(define* (install-dependencies . _)
> +  "Install dependencies."
> +  (setenv "ERL_LIBS" (getenv "GUIX_ERL_LIBS")))
Why not do this as part of setting up mix-env?

> +(define* (build #:key mix-environments #:allow-other-keys)
> +  "Builds the Mix project."
> +  (for-each (lambda (mix-env)
> +              (setenv "MIX_ENV" mix-env)
> +              (invoke "mix" "compile" "--no-deps-check"))
> +            mix-environments))
> +
> +(define* (check #:key (tests? #t) #:allow-other-keys)
> +  "Test the Mix project."
> +  (if tests?
> +      (invoke "mix" "test" "--no-deps-check")
> +      (format #t "tests? = ~a~%" tests?)))
> +
> +(define* (remove-mix-dirs . _)
> +  "Remove all .mix/ directories.
> +We do not want to copy them to the installation directory."
> +  (for-each delete-file-recursively
> +            (find-files "." (file-name-predicate "\\.mix$")
> #:directories? #t)))
> +
> +(define (package-name-version->elixir-name name+ver)
> +  "Convert the Guix package NAME-VER to the corresponding Elixir
> name-version
> +format.  Essentially drop the prefix used in Guix and replace dashes
> by
> +underscores."
> +  (let* ((name- (package-name->name+version name+ver)))
> +    (string-join
> +     (string-split
> +      (if (string-prefix? "elixir-" name-)
> +          (string-drop name- (string-length "elixir-"))
> +          name-)
> +      #\-)
> +     "_")))
> +
> +(define* (install #:key
> +                  inputs
> +                  outputs
> +                  name
> +                  build-per-environment
> +                  #:allow-other-keys)
> +  "Install build artifacts in the store."
> +  (let* ((lib-name (package-name-version->elixir-name name))
> +         (lib-dir (string-append (elixir-libdir (assoc-ref outputs
> "out")) "/" lib-name)))
> +    (mkdir-p lib-dir)
> +    (copy-recursively (string-append (mix-build-dir (if build-per-
> environment "prod" "shared")) "/" lib-name)
> +                      lib-dir
> +                      #:follow-symlinks? #t)))
> +
> +(define %standard-phases
> +  (modify-phases gnu:%standard-phases
> +    (delete 'bootstrap)
> +    (delete 'configure)
> +    (add-after 'install-locale 'set-mix-env set-mix-env)
> +    (replace 'unpack unpack)
> +    (replace 'build build)
> +    (replace 'check check)
> +    (add-before 'install 'remove-mix-dirs remove-mix-dirs)
> +    (replace 'install install)))
> +
> +(define* (mix-build #:key inputs (phases %standard-phases)
> +                    #:allow-other-keys #:rest args)
> +  "Build the given Mix package, applying all of PHASES in order."
> +  (apply gnu:gnu-build #:inputs inputs #:phases phases args))
> +
> +;;; mix-build-system.scm ends here
> 
> base-commit: a0d337e79c87d7c38c79d0291974f490cb137a52
Cheers
Pierre-Henry Fröhring Nov. 18, 2023, 4:44 a.m. UTC | #2
* WAITING Comment
** lilyp
> * guix/build-system/mix.scm,
> * guix/build/mix-build-system.scm: New modules.
Avoid spanning a change across multiple files.  Use
file: Change.
file2: Likewise.

>  gnu/packages/elixir.scm         |  62 ++++++++++-
>  guix/build-system/mix.scm       | 187
> ++++++++++++++++++++++++++++++++
>  guix/build/mix-build-system.scm | 171 +++++++++++++++++++++++++++++
You committed two changes at once here.  Split them.


** phf
How are changes counted? I've counted one because all of these changes
are needed
to introduce the ~mix-build-system~. Should it be:
#+begin_example
,* gnu/packages/elixir.scm (elixir): Search path GUIX_ELIXIR_LIBS added.
,* gnu/packages/elixir.scm (elixir): Wrap programs with
ERL_LIBS=${GUIX_ELIXIR_LIBS}.
,* guix/build-system/mix.scm: New modules.
,* guix/build/mix-build-system.scm: New modules.
#+end_example
or something else?


* WAITING Comment
** lilyp
> +(define* (set-mix-env #:key inputs mix-path mix-exs #:allow-other-
> keys)
> +  "Set environment variables.
> +See:
> https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables"
> +  (setenv "MIX_HOME" (getcwd))
> +  (setenv "MIX_ARCHIVES" "archives")
> +  (setenv "MIX_BUILD_ROOT" "_build")
> +  (setenv "MIX_DEPS_PATH" "deps")
> +  (setenv "MIX_PATH" (or mix-path ""))
> +  (setenv "MIX_REBAR3" (string-append (assoc-ref inputs "rebar3")
> "/bin/rebar3"))
> +  (setenv "MIX_EXS" mix-exs)
> +  (%elixir-version (elixir-version inputs)))
%elixir-version is not an environment variable.  You should set this up
separately or at the very least add a big fat comment explaining what
you're doing here :)


** phf
What about both?
#+begin_src scheme
(define* (set-elixir-version #:key inputs #:allow-other-keys)
  "Set Elixir version.
Compiled libraries are specific to each Elixir version. If a library is intended
to be used with multiple Elixir versions, it needs to be compiled separately for
each version. The parameter %elixir-version, set for the current build, is used
to differentiate between the compiled versions of libraries corresponding to
different Elixir versions."
  (%elixir-version (elixir-version inputs))
  (format #t "Elixir version: ~a~%" (%elixir-version)))
#+end_src


* WAITING Comment
** lilyp
> +     (list (search-path-specification
> +            (variable "GUIX_ERL_LIBS")
> +            (files (list "lib/erlang/lib"
> +                         (string-append "lib/elixir/" (version-
> major+minor version)))))))
I suppose ERL is for erlang?  What do we use for elixir then?


** phf
Changed for ~GUIX_ELIXIR_LIBS~. Is that OK?


* WAITING Comment
** lilyp
> +(define* (install-dependencies . _)
> +  "Install dependencies."
> +  (setenv "ERL_LIBS" (getenv "GUIX_ERL_LIBS")))
Why not do this as part of setting up mix-env?


** phf
If we have this phase in the ~elixir~ package:
#+begin_src scheme
(add-after 'install 'wrap-programs
            (lambda* (#:key inputs outputs #:allow-other-keys)
              (let* ((out (assoc-ref outputs "out"))
                     (programs '("elixir" "elixirc" "iex" "mix")))
                (for-each (lambda (program)
                            (wrap-program (string-append out "/bin/" program)
                              '("ERL_LIBS" prefix ("${GUIX_ELIXIR_LIBS}"))))
                          programs))))
#+end_src

and this native search path:
#+begin_src scheme
(search-path-specification
            (variable "GUIX_ELIXIR_LIBS")
            (files (list "lib/erlang/lib"
                         (string-append "lib/elixir/"
(version-major+minor version)))))
#+end_src

then, ~(setenv "ERL_LIBS" (getenv "GUIX_ERL_LIBS"))~ is not needed anymore.
Liliana Marie Prikler Nov. 18, 2023, 7:12 a.m. UTC | #3
Am Samstag, dem 18.11.2023 um 05:44 +0100 schrieb Pierre-Henry
Fröhring:
> * WAITING Comment
> ** lilyp
> > * guix/build-system/mix.scm,
> > * guix/build/mix-build-system.scm: New modules.
> Avoid spanning a change across multiple files.  Use
> file: Change.
> file2: Likewise.
> 
> >  gnu/packages/elixir.scm         |  62 ++++++++++-
> >  guix/build-system/mix.scm       | 187
> > ++++++++++++++++++++++++++++++++
> >  guix/build/mix-build-system.scm | 171
> > +++++++++++++++++++++++++++++
> You committed two changes at once here.  Split them.
> 
> 
> ** phf
> How are changes counted? I've counted one because all of these
> changes
> are needed
> to introduce the ~mix-build-system~. Should it be:
> #+begin_example
> ,* gnu/packages/elixir.scm (elixir): Search path GUIX_ELIXIR_LIBS
> added.
> ,* gnu/packages/elixir.scm (elixir): Wrap programs with
> ERL_LIBS=${GUIX_ELIXIR_LIBS}.
> ,* guix/build-system/mix.scm: New modules.
> ,* guix/build/mix-build-system.scm: New modules.
> #+end_example
> or something else?
You can add the two build-system files in one go.  The changes to
elixir and the new elixir-hex package are one patch each.

> * WAITING Comment
> ** lilyp
> > +(define* (set-mix-env #:key inputs mix-path mix-exs #:allow-other-
> > keys)
> > +  "Set environment variables.
> > +See:
> > https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables
> > "
> > +  (setenv "MIX_HOME" (getcwd))
> > +  (setenv "MIX_ARCHIVES" "archives")
> > +  (setenv "MIX_BUILD_ROOT" "_build")
> > +  (setenv "MIX_DEPS_PATH" "deps")
> > +  (setenv "MIX_PATH" (or mix-path ""))
> > +  (setenv "MIX_REBAR3" (string-append (assoc-ref inputs "rebar3")
> > "/bin/rebar3"))
> > +  (setenv "MIX_EXS" mix-exs)
> > +  (%elixir-version (elixir-version inputs)))
> %elixir-version is not an environment variable.  You should set this
> up
> separately or at the very least add a big fat comment explaining what
> you're doing here :)
> 
> 
> ** phf
> What about both?
> #+begin_src scheme
> (define* (set-elixir-version #:key inputs #:allow-other-keys)
>   "Set Elixir version.
> Compiled libraries are specific to each Elixir version. If a library
> is intended
> to be used with multiple Elixir versions, it needs to be compiled
> separately for
> each version. The parameter %elixir-version, set for the current
> build, is used
> to differentiate between the compiled versions of libraries
> corresponding to
> different Elixir versions."
>   (%elixir-version (elixir-version inputs))
>   (format #t "Elixir version: ~a~%" (%elixir-version)))
> #+end_src
I just noticed that, but do prefer to spaces after sentence ending
periods.  For the docstring, I think you should make clear what is
going on from the beginning, e.g. 
"Store the version number of the elixir input in a parameter." 

> * WAITING Comment
> ** lilyp
> > +     (list (search-path-specification
> > +            (variable "GUIX_ERL_LIBS")
> > +            (files (list "lib/erlang/lib"
> > +                         (string-append "lib/elixir/" (version-
> > major+minor version)))))))
> I suppose ERL is for erlang?  What do we use for elixir then?
> 
> 
> ** phf
> Changed for ~GUIX_ELIXIR_LIBS~. Is that OK?
No, because it's still ERL on the other side.  A quick web search
reveals that this belongs to erlang. 

> 
> * WAITING Comment
> ** lilyp
> > +(define* (install-dependencies . _)
> > +  "Install dependencies."
> > +  (setenv "ERL_LIBS" (getenv "GUIX_ERL_LIBS")))
> Why not do this as part of setting up mix-env?
> 
> 
> ** phf
> If we have this phase in the ~elixir~ package:
> #+begin_src scheme
> (add-after 'install 'wrap-programs
>             (lambda* (#:key inputs outputs #:allow-other-keys)
>               (let* ((out (assoc-ref outputs "out"))
>                      (programs '("elixir" "elixirc" "iex" "mix")))
>                 (for-each (lambda (program)
>                             (wrap-program (string-append out "/bin/"
> program)
>                               '("ERL_LIBS" prefix
> ("${GUIX_ELIXIR_LIBS}"))))
>                           programs))))
> #+end_src
> 
> and this native search path:
> #+begin_src scheme
> (search-path-specification
>             (variable "GUIX_ELIXIR_LIBS")
>             (files (list "lib/erlang/lib"
>                          (string-append "lib/elixir/"
> (version-major+minor version)))))
> #+end_src
> 
> then, ~(setenv "ERL_LIBS" (getenv "GUIX_ERL_LIBS"))~ is not needed
> anymore.
True, but it's still given in the source files. :)
So you can delete it of course (if it's already done by the native-
search-path and package as you claim), or you can make it part of the
environment setup (if it's not).

Cheers
Pierre-Henry Fröhring Nov. 18, 2023, 10:19 a.m. UTC | #4
> > Changed for ~GUIX_ELIXIR_LIBS~. Is that OK?
> No, because it's still ERL on the other side.  A quick web search
> reveals that this belongs to erlang.
Yes, ERL_LIBS is valid for both Erlang and Elixir.
So, GUIX_ERL_LIBS for both, right?
Liliana Marie Prikler Nov. 18, 2023, 11:11 a.m. UTC | #5
Am Samstag, dem 18.11.2023 um 11:19 +0100 schrieb Pierre-Henry
Fröhring:
> > > Changed for ~GUIX_ELIXIR_LIBS~. Is that OK?
> > No, because it's still ERL on the other side.  A quick web search
> > reveals that this belongs to erlang.
> Yes, ERL_LIBS is valid for both Erlang and Elixir.
> So, GUIX_ERL_LIBS for both, right?
I'm not sure whether we really need the GUIX_ prefix here; you'd have
to check whether the justification for GUIX_PYTHONPATH also holds for
erlang and elixir.

Anyhow, assuming that elixir and erlang code can be used interchangedly
by each once compiled, you should use a single (GUIX_)?ERL_LIBS in both
packages.  You might also see whether we could use a single path
instead of needing the lib/erlang and lib/elixir split, but I leave
that up to you.

Cheers
Pierre-Henry Fröhring Nov. 18, 2023, 12:02 p.m. UTC | #6
** lilyp
I'm not sure whether we really need the GUIX_ prefix here; you'd have to check
whether the justification for GUIX_PYTHONPATH also holds for erlang and
elixir.


*** phf
Replacing "Python" by "Elixir" in
https://guix.gnu.org/manual/en/html_node/Build-Systems.html, we have:
#+begin_quote
For packages that install stand-alone Elixir programs under bin/, it takes
care of wrapping these programs so that their GUIX_ERL_LIBS environment
variable points to all the compiled Elixir and Erlang libraries they depend
on.
#+end_quote
This seems reasonable for Elixir stand-alone programs.


#+begin_quote
Which Elixir package is used to perform the build can be specified with the
#:elixir parameter. This is a useful way to force a package to be built for a
specific version of the Elixir interpreter, which might be necessary if the
package is only compatible with a single interpreter version.
#+end_quote
Reading: https://hexdocs.pm/elixir/1.15.7/compatibility-and-deprecations.html,
this seems reasonable too.


This seems to point to GUIX_ERL_LIBS rather than ERL_LIBS.


** lilyp
Anyhow, assuming that elixir and erlang code can be used interchangeably by
each once compiled, you should use a single (GUIX_)?ERL_LIBS in both packages.
You might also see whether we could use a single path instead of needing the
lib/erlang and lib/elixir split, but I leave that up to you.


*** phf
"elixir and erlang code can be used interchangeably by each once compiled" is
true because: "Elixir compiles into BEAM byte code (via Erlang Abstract
Format). This means that Elixir code can be called from Erlang and vice versa,
without the need to write any bindings." See:
https://elixir-lang.org/crash-course.html#adding-elixir-to-existing-erlang-programs


Ha… finally, we see that things should change on the Erlang side too.
According to
https://hexdocs.pm/elixir/1.15.7/compatibility-and-deprecations.html, to a
version of Elixir are associated with a few versions of compatible
Erlang/OTP. Since the Elixir package mentions a particular version of Erlang,
then it means that we have X.Y version of Elixir and U.V version for Erlang.
If we have a single path like lib/beam/A.B, then A.B is rather ambiguous.  If
instead we have lib/erlang/U.V and lib/elixir/X.Y, then no ambiguities.


This seems to point to lib/erlang/U.V and lib/elixir/X.Y.

Cheers
diff mbox series

Patch

diff --git a/gnu/packages/elixir.scm b/gnu/packages/elixir.scm
index 724b4251..6f779afb 100644
--- a/gnu/packages/elixir.scm
+++ b/gnu/packages/elixir.scm
@@ -27,6 +27,7 @@  (define-module (gnu packages elixir)
   #:use-module ((guix licenses) #:prefix license:)
   #:use-module (guix build-system gnu)
   #:use-module (guix gexp)
+  #:use-module (guix utils)
   #:use-module (guix git-download)
   #:use-module (guix packages)
   #:use-module (gnu packages)
@@ -96,9 +97,21 @@  (define-public elixir
             (lambda* (#:key inputs #:allow-other-keys)
               ;; Some tests require access to a home directory.
               (setenv "HOME" "/tmp")))
-          (delete 'configure))))
-    (inputs
-     (list erlang git))
+          (delete 'configure)
+          (add-after 'install 'wrap-programs
+            (lambda* (#:key inputs outputs #:allow-other-keys)
+              (let* ((out (assoc-ref outputs "out"))
+                     (programs '("elixir" "elixirc" "iex" "mix")))
+                (for-each (lambda (program)
+                            (wrap-program (string-append out "/bin/" program)
+                              '("ERL_LIBS" prefix ("${GUIX_ERL_LIBS}"))))
+                          programs)))))))
+    (inputs (list erlang git))
+    (native-search-paths
+     (list (search-path-specification
+            (variable "GUIX_ERL_LIBS")
+            (files (list "lib/erlang/lib"
+                         (string-append "lib/elixir/" (version-major+minor version)))))))
     (home-page "https://elixir-lang.org/")
     (synopsis "Elixir programming language")
     (description "Elixir is a dynamic, functional language used to build
@@ -106,3 +119,46 @@  (define-public elixir
 for running low-latency, distributed and fault-tolerant systems, while also
 being successfully used in web development and the embedded software domain.")
     (license license:asl2.0)))
+
+(define-public elixir-hex
+  (package
+    (name "elixir-hex")
+    (version "2.0.5")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+             (url "https://github.com/hexpm/hex.git")
+             (commit (string-append "v" version))))
+       (file-name (git-file-name name version))
+       (sha256
+        (base32
+         "1kvczwvij58kgkhak68004ap81pl26600bczg21mymy2sypkgxmj"))))
+    ;; The mix-build-system assumes that Hex exists.
+    ;; We build Hex using the gnu-build-system.
+    ;; Other Elixir packages use the mix-build-system.
+    (build-system gnu-build-system)
+    (inputs (list elixir))
+    (arguments
+     (list
+      #:phases
+      #~(modify-phases %standard-phases
+          (delete 'bootstrap)
+          (delete 'configure)
+          (replace 'build
+            (lambda* (#:key inputs #:allow-other-keys)
+              (setenv "MIX_ENV" "prod")
+              (invoke "mix" "compile")))
+          (delete 'check)
+          (replace 'install
+            (lambda* (#:key inputs outputs #:allow-other-keys)
+              (define X.Y #$(version-major+minor (package-version elixir)))
+              (define out (string-append (assoc-ref outputs "out") "/lib/elixir/" X.Y "/hex"))
+              (mkdir-p out)
+              (copy-recursively "_build/prod/lib/hex" out))))))
+    (synopsis "Package manager for the Erlang VM")
+    (description
+     "This project provides tasks that integrate with Mix, Elixir's build
+tool.")
+    (home-page "https://hexdocs.pm/makeup_elixir/")
+    (license license:bsd-2)))
diff --git a/guix/build-system/mix.scm b/guix/build-system/mix.scm
new file mode 100644
index 00000000..7ddedea9
--- /dev/null
+++ b/guix/build-system/mix.scm
@@ -0,0 +1,187 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2023 Pierre-Henry Fröhring <phfrohring@deeplinks.com>
+;;;
+;;; 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/>.
+
+;; Commentary:
+;;
+;; Standard build procedure for Elixir packages using 'mix'.  This is
+;; implemented as an extension of 'gnu-build-system'.
+;;
+;; Code:
+
+(define-module (guix build-system mix)
+  #:use-module (guix build mix-build-system)
+  #:use-module (guix build-system gnu)
+  #:use-module (guix build-system)
+  #:use-module (guix gexp)
+  #:use-module (guix monads)
+  #:use-module (guix packages)
+  #:use-module (guix search-paths)
+  #:use-module (guix store)
+  #:use-module (guix utils)
+  #:use-module (ice-9 match)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:export (mix-build-system hexpm-uri))
+
+;; Lazily resolve the bindings to avoid circular dependencies.
+(define (default-glibc-utf8-locales)
+  (let* ((base (resolve-interface '(gnu packages base))))
+    (module-ref base 'glibc-utf8-locales)))
+
+(define (default-elixir-hex)
+  (let ((elixir (resolve-interface '(gnu packages elixir))))
+    (module-ref elixir 'elixir-hex)))
+
+(define (default-rebar3)
+  (let ((erlang (resolve-interface '(gnu packages erlang))))
+    (module-ref erlang 'rebar3)))
+
+(define (default-elixir)
+  "Return the default Elixir package."
+  (let ((elixir (resolve-interface '(gnu packages elixir))))
+    (module-ref elixir 'elixir)))
+
+(define* (strip-prefix name #:optional (prefix "elixir-"))
+  "Return NAME without the prefix PREFIX."
+  (if (string-prefix? prefix name)
+      (string-drop name (string-length prefix))
+      name))
+
+(define (hexpm-uri name version)
+  "Return the URI where to fetch the sources of a Hex package NAME at VERSION.
+NAME is the name of the package which should look like: elixir-pkg-name-X.Y.Z
+See: https://github.com/hexpm/specifications/blob/main/endpoints.md"
+  ((compose
+    (cut string-append "https://repo.hex.pm/tarballs/" <> "-" version ".tar")
+    (cut string-replace-substring <> "-" "_")
+    strip-prefix)
+   name))
+
+;; A number of environment variables specific to the Mix build system are
+;; reflected here.  They are documented at
+;; https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables.  Other
+;; parameters located in mix.exs are defined at
+;; https://hexdocs.pm/mix/1.15.7/Mix.Project.html#module-configuration
+(define* (mix-build name
+                    inputs
+                    #:key
+                    source
+                    (tests? #t)
+                    (mix-path #f) ;See MIX_PATH.
+                    (mix-exs "mix.exs") ;See MIX_EXS.
+                    (build-per-environment #t) ;See :build_per_environment.
+                    (phases '%standard-phases)
+                    (outputs '("out"))
+                    (search-paths '())
+                    (system (%current-system))
+                    (guile #f)
+                    (imported-modules `((guix build mix-build-system)
+                                        ,@%gnu-build-system-modules))
+                    (modules '((guix build mix-build-system)
+                               (guix build utils))))
+  "Build SOURCE using Elixir, and with INPUTS."
+
+  ;; Check the documentation of :build_per_environment here:
+  ;; https://hexdocs.pm/mix/1.15.7/Mix.Project.html#module-configuration And
+  ;; "Environments" here:
+  ;; https://hexdocs.pm/mix/1.15.7/Mix.html#module-environments
+  (define mix-environments
+    (if build-per-environment
+        `("prod" ,@(if tests? '("test") '()))
+        '("shared")))
+
+  (define builder
+    (with-imported-modules imported-modules
+      #~(begin
+
+          (use-modules #$@(sexp->gexp modules))
+
+          #$(with-build-variables inputs outputs
+              #~(mix-build #:name #$name
+                           #:source #+source
+                           #:system #$system
+                           #:tests? #$tests?
+                           #:mix-path #$mix-path
+                           #:mix-exs #$mix-exs
+                           #:mix-environments '#$mix-environments
+                           #:build-per-environment #$build-per-environment
+                           #:phases #$(if (pair? phases)
+                                          (sexp->gexp phases)
+                                          phases)
+                           #:outputs %outputs
+                           #:search-paths '#$(sexp->gexp
+                                              (map
+                                               search-path-specification->sexp
+                                               search-paths))
+                           #:inputs
+                           %build-inputs)))))
+
+  (mlet %store-monad ((guile (package->derivation (or guile (default-guile))
+                                                  system
+                                                  #:graft? #f)))
+    (gexp->derivation name
+                      builder
+                      #:system system
+                      #:graft? #f       ;consistent with 'gnu-build'
+                      #:target #f
+                      #:guile-for-build guile)))
+
+(define* (lower name
+                #:key
+                (elixir (default-elixir))
+                (elixir-hex (default-elixir-hex))
+                (glibc-utf8-locales (default-glibc-utf8-locales))
+                (inputs '())
+                (native-inputs '())
+                (propagated-inputs '())
+                (rebar3 (default-rebar3))
+                (tests? #t)
+                outputs
+                source
+                system
+                target
+                #:allow-other-keys #:rest arguments)
+  "Return a bag for NAME."
+  (let ((private-keywords
+         '(#:inputs #:native-inputs
+           #:outputs #:system #:target
+           #:elixir #:elixir-hex #:glibc-utf8-locales
+           #:rebar3 #:erlang))
+        (build-inputs
+         `(,@(standard-packages)
+           ("glibc-utf8-locales" ,glibc-utf8-locales)
+           ("erlang" ,(lookup-package-input elixir "erlang"))
+           ("rebar3" ,rebar3)
+           ("elixir" ,elixir)
+           ("elixir-hex" ,elixir-hex)
+           ,@inputs
+           ,@native-inputs)))
+  (bag (name name)
+       (system system)
+       (build-inputs build-inputs)
+       (host-inputs (if target inputs '()))
+       (outputs outputs)
+       (build mix-build)
+       (arguments (strip-keyword-arguments private-keywords arguments)))))
+
+(define mix-build-system
+  (build-system (name 'mix)
+                (description "The standard Mix build system")
+                (lower lower)))
+
+;;; mix.scm ends here
diff --git a/guix/build/mix-build-system.scm b/guix/build/mix-build-system.scm
new file mode 100644
index 00000000..ca649582
--- /dev/null
+++ b/guix/build/mix-build-system.scm
@@ -0,0 +1,171 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2023 Pierre-Henry Fröhring <phfrohring@deeplinks.com>
+;;;
+;;; 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/>.
+
+;; Commentary:
+;;
+;; Code:
+
+(define-module (guix build mix-build-system)
+  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
+  #:use-module (guix build utils)
+  #:use-module (ice-9 ftw)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 string-fun)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-71)
+  #:export (mix-build
+            %standard-phases))
+
+;; The Elixir version is constant as soon as it is computable from the current
+;; execution. It is a X.Y string where X and Y are respectively the major and
+;; minor version number of the Elixir used in the build.
+(define %elixir-version (make-parameter "X.Y"))
+
+(define* (elixir-libdir path #:optional (version (%elixir-version)))
+  "Return the path where all libraries for a specified ELIXIR version are installed."
+  (string-append path "/lib/elixir/" version))
+
+(define (erlang-libdir path)
+  "Return the path of the directory where libraries of an Erlang package are
+installed in the store."
+  (string-append path "/lib/erlang/lib"))
+
+(define* (strip-prefix name #:optional (prefix "elixir-"))
+  "Return NAME without the prefix PREFIX."
+  (if (string-prefix? prefix name)
+      (string-drop name (string-length prefix))
+      name))
+
+(define (mix-build-dir mix-env)
+  "Return the directory where build artifacts are to be installed according to
+en environment MIX-ENV in the current directory."
+  (string-append "_build/" mix-env "/lib"))
+
+(define (elixir-version inputs)
+  "Return an X.Y string where X and Y are respectively the major and minor version number of ELIXIR.
+Example: /gnu/store/…-elixir-1.14.0 → 1.14"
+  (let* ((_ version (package-name->name+version
+                     (strip-store-file-name (assoc-ref inputs "elixir"))))
+         (components  (string-split version #\.))
+         (major+minor (take components 2)))
+    (string-join major+minor ".")))
+
+(define* (unpack #:key source mix-path #:allow-other-keys)
+  "Unpack SOURCE in the working directory, and change directory within the
+source.  When SOURCE is a directory, copy it in a sub-directory of the current
+working directory."
+  (let ((gnu-unpack (assoc-ref gnu:%standard-phases 'unpack)))
+    (gnu-unpack #:source source)
+    (when (file-exists? "contents.tar.gz")
+      (invoke "tar" "xvf" "contents.tar.gz"))))
+
+(define (list-directories dir)
+  "List absolute paths of directories directly under the directory DIR."
+  (map (cut string-append dir "/" <>)
+       (scandir dir (lambda (filename)
+                      (and (not (member filename '("." "..")))
+                           (directory-exists? (string-append dir "/" filename)))))))
+
+(define* (set-mix-env #:key inputs mix-path mix-exs #:allow-other-keys)
+  "Set environment variables.
+See: https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables"
+  (setenv "MIX_HOME" (getcwd))
+  (setenv "MIX_ARCHIVES" "archives")
+  (setenv "MIX_BUILD_ROOT" "_build")
+  (setenv "MIX_DEPS_PATH" "deps")
+  (setenv "MIX_PATH" (or mix-path ""))
+  (setenv "MIX_REBAR3" (string-append (assoc-ref inputs "rebar3") "/bin/rebar3"))
+  (setenv "MIX_EXS" mix-exs)
+  (%elixir-version (elixir-version inputs)))
+
+(define* (install-hex #:key name inputs outputs #:allow-other-keys)
+  "Install Hex."
+  (let ((hex-archive-path (string-append (getenv "MIX_ARCHIVES") "/hex")))
+    (mkdir-p hex-archive-path)
+    (symlink (car (list-directories (elixir-libdir (assoc-ref inputs "elixir-hex"))))
+             (string-append hex-archive-path "/hex"))))
+
+(define* (install-dependencies . _)
+  "Install dependencies."
+  (setenv "ERL_LIBS" (getenv "GUIX_ERL_LIBS")))
+
+(define* (build #:key mix-environments #:allow-other-keys)
+  "Builds the Mix project."
+  (for-each (lambda (mix-env)
+              (setenv "MIX_ENV" mix-env)
+              (invoke "mix" "compile" "--no-deps-check"))
+            mix-environments))
+
+(define* (check #:key (tests? #t) #:allow-other-keys)
+  "Test the Mix project."
+  (if tests?
+      (invoke "mix" "test" "--no-deps-check")
+      (format #t "tests? = ~a~%" tests?)))
+
+(define* (remove-mix-dirs . _)
+  "Remove all .mix/ directories.
+We do not want to copy them to the installation directory."
+  (for-each delete-file-recursively
+            (find-files "." (file-name-predicate "\\.mix$") #:directories? #t)))
+
+(define (package-name-version->elixir-name name+ver)
+  "Convert the Guix package NAME-VER to the corresponding Elixir name-version
+format.  Essentially drop the prefix used in Guix and replace dashes by
+underscores."
+  (let* ((name- (package-name->name+version name+ver)))
+    (string-join
+     (string-split
+      (if (string-prefix? "elixir-" name-)
+          (string-drop name- (string-length "elixir-"))
+          name-)
+      #\-)
+     "_")))
+
+(define* (install #:key
+                  inputs
+                  outputs
+                  name
+                  build-per-environment
+                  #:allow-other-keys)
+  "Install build artifacts in the store."
+  (let* ((lib-name (package-name-version->elixir-name name))
+         (lib-dir (string-append (elixir-libdir (assoc-ref outputs "out")) "/" lib-name)))
+    (mkdir-p lib-dir)
+    (copy-recursively (string-append (mix-build-dir (if build-per-environment "prod" "shared")) "/" lib-name)
+                      lib-dir
+                      #:follow-symlinks? #t)))
+
+(define %standard-phases
+  (modify-phases gnu:%standard-phases
+    (delete 'bootstrap)
+    (delete 'configure)
+    (add-after 'install-locale 'set-mix-env set-mix-env)
+    (replace 'unpack unpack)
+    (replace 'build build)
+    (replace 'check check)
+    (add-before 'install 'remove-mix-dirs remove-mix-dirs)
+    (replace 'install install)))
+
+(define* (mix-build #:key inputs (phases %standard-phases)
+                    #:allow-other-keys #:rest args)
+  "Build the given Mix package, applying all of PHASES in order."
+  (apply gnu:gnu-build #:inputs inputs #:phases phases args))
+
+;;; mix-build-system.scm ends here