;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2023 Pierre-Henry Fröhring ;;; ;;; 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 . ;; 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) #:export (mix-build strip-elixir-prefix %standard-phases)) (define (elixir-version elixir) "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" ((compose (cut string-join <> ".") (cut take <> 2) (cut string-split <> #\.) last) (string-split elixir #\-))) (define (elixir-libdir elixir path) "Return the path where all libraries for a specified ELIXIR version are installed." (string-append path "/lib/elixir/" (elixir-version elixir))) (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 (install-dir inputs outputs) "Return the path of the current output's Elixir library. Example: /gnu/store/…/lib/elixir/1.14" (elixir-libdir (assoc-ref inputs "elixir") (assoc-ref outputs "out"))) (define (strip-prefix prefix name) "Return NAME without the prefix PREFIX." (if (string-prefix? prefix name) (string-drop name (string-length prefix)) name)) (define (strip-elixir-prefix name) "Strip elixir- from NAME." (strip-prefix "elixir-" 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* (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* (configure #: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 "LC_ALL" "en_US.UTF-8") (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)) (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") (assoc-ref inputs "elixir-hex")))) (string-append hex-archive-path "/hex")))) (define* (install-dependencies #:key name inputs outputs tests? build-per-environment (native-inputs '()) mix-environments #:allow-other-keys #:rest rest) "Install dependencies." (define (install-lib lib dir) (let ((lib-name (last (string-split lib #\/)))) (symlink lib (string-append dir "/" lib-name)))) (define (install-input mix-env input) (let ((dir (mix-build-dir mix-env))) (mkdir-p dir) (match input ((_ . path) ((compose (cut for-each (cut install-lib <> dir) <>) (cut append-map list-directories <>) (cut filter directory-exists? <>)) (list (elixir-libdir (assoc-ref inputs "elixir") path) (erlang-libdir path))))))) (define (install-inputs mix-env) (for-each (cut install-input mix-env <>) (append inputs native-inputs))) (for-each install-inputs mix-environments)) (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 (library-name pkg-name) "Return the library name deduced from PKG-NAME. A package should be named: elixir-lib-name-X.Y.Z from which the library name lib_name is deduced." ((compose (cut string-join <> "_") (cut drop-right <> 1) (cut string-split <> #\-)) (strip-elixir-prefix pkg-name))) (define* (install #:key inputs outputs name build-per-environment #:allow-other-keys) "Install build artifacts in the store." (let* ((lib-name (library-name name)) (lib-dir (string-append (install-dir inputs outputs) "/" 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) (replace 'configure configure) (replace 'unpack unpack) (add-after 'patch-generated-file-shebangs 'install-hex install-hex) (add-after 'install-hex 'install-dependencies install-dependencies) (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