guix_mirror_bot pushed a commit to branch beam-team
in repository guix.

commit 875223f9d4dc589d504d9bb8d73c3b2a5c452e97
Author: Igorj Gorjaĉev <[email protected]>
AuthorDate: Fri Jan 9 14:16:50 2026 +0200

    build-system: mix: Add support for importing deps from 'mix.lock'.
    
    * guix/import/mix.scm: New file.
    * guix/import/mix/mix-lock.scm: New file.
    * guix/scripts/import/mix.scm: New file.
    * tests/import/mix.scm: New file.
    * Makefile.am (MODULES, SCM_TESTS): Register them.
    * etc/teams.scm (beam): Likewise.
    * gnu/packages/beam-apps.scm: New file.
    * gnu/packages/beam-packages.scm: New file.
    * gnu/packages/beam-sources.scm: New file.
    * gnu/local.mk (GNU_SYSTEM_MODULES): Register them.
    * etc/teams.scm (beam): Likewise.
    * guix/build-system/mix.scm (define-mix-inputs): New macro.
    (beam-name->package-name, hexpm-source, mix-inputs): New procedures.
    (mix-build): Add new variables.
    * guix/build/mix-build-system.scm (unpack-vendorize,
    symlink-vendorize, beam-package?): New procedures.
    (%standard-phases): Add new phases.
    (build, check, install): Add vendoring support.
    (%elixir-prefix, %beam-prefix, %vendorize-env-var): Add new symbols.
    (strip-prefix, package-name->elixir-name): Add support for beam-prefixed
    packages.
    * guix/scripts/import.scm (process-args): Add 'mix' importer.
    * etc/teams.scm (beam)<#:description>: Add Mix importer mention.
    * etc/teams.scm (beam)<#:scope>: Add "tests/import/hexpm.scm".
    * CODEOWNERS: Regenerate file.
    * guix/import/hexpm.scm (dependencies->package-names,
    hexpm->guix-package): Add optional mix-inputs support.
    * guix/scripts/import/hexpm.scm (show-help, %options,
    parse-options): Likewise.
    * tests/import/hexpm.scm: Likewise.
    * guix/import/hexpm.scm (hexpm->guix-package): Fix typo.
    * doc/guix.texi: Add initial documentation for mix importer and
    new option for hexpm importer.
    * doc/guix-cookbook.texi: Add example of packaging Elixir app.
    
    Change-Id: I5d11f8be111963fbac2fd6ab5fd0bd644f9488b0
    Signed-off-by: Giacomo Leidi <[email protected]>
---
 CODEOWNERS                      |   6 ++
 Makefile.am                     |   5 +
 doc/guix-cookbook.texi          | 105 +++++++++++++++++++++
 doc/guix.texi                   |  26 ++++++
 etc/teams.scm                   |  12 ++-
 gnu/local.mk                    |   4 +
 gnu/packages/beam-apps.scm      |  25 +++++
 gnu/packages/beam-packages.scm  |  43 +++++++++
 gnu/packages/beam-sources.scm   |  29 ++++++
 guix/build-system/mix.scm       |  59 +++++++++++-
 guix/build/mix-build-system.scm | 137 ++++++++++++++++++++++-----
 guix/import/hexpm.scm           |  59 ++++++------
 guix/import/mix.scm             | 143 ++++++++++++++++++++++++++++
 guix/import/mix/mix-lock.scm    | 201 ++++++++++++++++++++++++++++++++++++++++
 guix/scripts/import.scm         |  20 ++--
 guix/scripts/import/hexpm.scm   |   9 +-
 guix/scripts/import/mix.scm     | 122 ++++++++++++++++++++++++
 tests/import/hexpm.scm          |  44 +++++++++
 tests/import/mix.scm            |  97 +++++++++++++++++++
 19 files changed, 1084 insertions(+), 62 deletions(-)

diff --git a/CODEOWNERS b/CODEOWNERS
index 8f0acc8933..fa2c50dbd8 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -12,6 +12,7 @@ gnu/packages/audio\.scm                            @guix/audio
 gnu/packages/fluidplug\.scm                        @guix/audio
 gnu/packages/xiph\.scm                             @guix/audio
 
+gnu/packages/beam-.+\.scm$                         @guix/beam
 gnu/packages/elixir(-.+|)\.scm$                    @guix/beam
 guix/build/mix-build-system\.scm                   @guix/beam
 guix/build-system/mix\.scm                         @guix/beam
@@ -19,7 +20,12 @@ gnu/packages/erlang(-.+|)\.scm$                    @guix/beam
 guix/build/rebar-build-system\.scm                 @guix/beam
 guix/build-system/rebar\.scm                       @guix/beam
 guix/import/hexpm\.scm                             @guix/beam
+guix/import/mix\.scm                               @guix/beam
+guix/import/mix/mix-lock\.scm                      @guix/beam
 guix/scripts/import/hexpm\.scm                     @guix/beam
+guix/scripts/import/mix\.scm                       @guix/beam
+tests/import/hexpm\.scm                            @guix/beam
+tests/import/mix\.scm                              @guix/beam
 
 gnu/packages/bioinformatics\.scm                   @guix/bioinformatics
 
diff --git a/Makefile.am b/Makefile.am
index 96b2acbcea..476e1f9864 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,6 +22,7 @@
 # Copyright © 2024 gemmaro <[email protected]>
 # Copyright © 2025 Brice Waegeneire <[email protected]>
 # Copyright © 2025 Florian Pelz <[email protected]>
+# Copyright © 2025 Igorj Gorjaĉev <[email protected]>
 #
 # This file is part of GNU Guix.
 #
@@ -319,6 +320,8 @@ MODULES =                                   \
   guix/import/launchpad.scm                    \
   guix/import/luanti.scm                       \
   guix/import/minetest.scm                     \
+  guix/import/mix.scm                          \
+  guix/import/mix/mix-lock.scm                 \
   guix/import/nuget.scm                        \
   guix/import/npm-binary.scm                   \
   guix/import/opam.scm                         \
@@ -376,6 +379,7 @@ MODULES =                                   \
   guix/scripts/import/json.scm                 \
   guix/scripts/import/luanti.scm               \
   guix/scripts/import/minetest.scm             \
+  guix/scripts/import/mix.scm                  \
   guix/scripts/import/npm-binary.scm           \
   guix/scripts/import/nuget.scm                \
   guix/scripts/import/opam.scm                 \
@@ -580,6 +584,7 @@ SCM_TESTS =                                 \
   tests/import/hackage.scm                     \
   tests/import/hexpm.scm                       \
   tests/import/luanti.scm                      \
+  tests/import/mix.scm                         \
   tests/import/npm-binary.scm                  \
   tests/import/nuget.scm                       \
   tests/import/opam.scm                                \
diff --git a/doc/guix-cookbook.texi b/doc/guix-cookbook.texi
index b04ca538ab..d8fa65bd64 100644
--- a/doc/guix-cookbook.texi
+++ b/doc/guix-cookbook.texi
@@ -29,6 +29,7 @@ Copyright @copyright{} 2025 45mg@*
 Copyright @copyright{} 2023 Marek Felšöci@*
 Copyright @copyright{} 2023 Konrad Hinsen@*
 Copyright @copyright{} 2023 Philippe Swartvagher@*
+Copyright @copyright{} 2026 Igorj Gorjaĉev@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -140,6 +141,7 @@ Programmable and automated package definition
 Packaging Workflows
 
 * Packaging Rust Crates::
+* Packaging Elixir Applications::
 
 Packaging Rust Crates
 
@@ -1629,6 +1631,7 @@ systems, serving as extensions to the concise packaging 
guidelines
 
 @menu
 * Packaging Rust Crates::
+* Packaging Elixir Applications::
 @end menu
 
 @node Packaging Rust Crates
@@ -1984,6 +1987,108 @@ method, one of the most popular choices for Traditional 
Chinese users.")
     (license license:lgpl2.1+)))
 @end lisp
 
+@node Packaging Elixir Applications
+@subsection Packaging Elixir Applications
+
+In preparation, add the following packages to your environment:
+
+@example
+$ guix shell elixir elixir-depscheck elixir-mix-audit
+@end example
+
+In this example, we package @uref{https://livebook.dev, livebook},
+which is an interactive, web-based notebook environment for writing,
+running, and sharing Elixir code with rich documentation and
+visualizations.
+
+@enumerate
+@item
+We retrieve the @code{livebook} source code with the latest version
+from the corresponding @code{git} repository:
+
+@example
+$ git clone --branch v0.18.2 --depth 1 \
+      https://github.com/livebook-dev/livebook
+@end example
+
+We should prepare the following definiton for this package (the suggested
+location for this definition should be @code{gnu/packages/beam-packages.scm}
+or up to your mind):
+
+@lisp
+(define-public livebook
+  (package
+    (name "livebook")
+    (version "0.18.2")
+    (source
+     (origin
+       (method git-fetch)
+       (uri (git-reference
+              (url "https://github.com/livebook-dev/livebook";)
+              (commit (string-append "v" version))))
+       (file-name (git-file-name name version))
+       (sha256
+        (base32
+         "1pq227nabslr7gh4qvg24wj4320djc0vrh6x6l23g3468x6znxjm"))))
+    (build-system mix-build-system)
+    (arguments
+     (list
+      #:tests? #f ; TODO: make them work a little bit later
+      #:vendorize? #t
+      #:phases
+      #~(modify-phases %standard-phases
+          (replace 'install
+            (lambda* (#:key outputs #:allow-other-keys)
+              (let ((out (assoc-ref outputs "out")))
+                (copy-recursively "_build/prod/rel/livebook/"
+                                  out)))))))
+    (inputs (mix-inputs 'livebook))
+    (synopsis "Web application for writing code notebooks")
+    (description "This package provides @code{Livebook}, a web application
+for writing interactive and collaborative code notebooks.")
+    (home-page "https://livebook.dev/";)
+    (license license:asl2.0)))
+@end lisp
+
+The identifier used to invoke @code{mix-inputs}, in this case the scheme
+symbol @code{'livebook}, must be unique, usually matching the variable name
+of the package.
+
+@item
+Navigate to the unpacked directory, then run the following commands:
+
+@example
+$ mix deps.get
+$ mix deps.audit
+$ mix depscheck --verbose
+@end example
+
+@command{mix deps.audit} checks known vulnerabilities and @command{mix
+depscheck --verbose} checks licenses, for all the dependencies.  We must
+have an acceptable output (i.e. message ``No vulnerabilities found.'') of
+@command{mix deps.audit} and ensure all dependencies are licensed with our
+supported licenses (@pxref{Defining Packages,,, guix, GNU Guix Reference 
Manual}).
+
+@item
+Import dependencies from the lockfile:
+
+@example
+$ guix import --insert=gnu/packages/beam-packages.scm \
+      mix --lockfile=/path/to/mix.lock livebook
+
+# or:
+$ guix import -i gnu/packages/beam-packages.scm \
+      mix -f /path/to/mix.lock livebook
+@end example
+
+After that the package could be build by using the following command:
+
+@example
+$ guix build livebook
+@end example
+
+@end enumerate
+
 
 @c *********************************************************************
 @node System Configuration
diff --git a/doc/guix.texi b/doc/guix.texi
index 4c113518fa..03e3280629 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -149,6 +149,7 @@ Copyright @copyright{} 2025 Rodion Goritskov@*
 Copyright @copyright{} 2025 dan@*
 Copyright @copyright{} 2025 Noé Lopez@*
 Copyright @copyright{} 2026 David Elsing@*
+Copyright @copyright{} 2026 Igorj Gorjaĉev@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -15349,6 +15350,31 @@ Additional options include:
 Traverse the dependency graph of the given upstream package recursively
 and generate package expressions for all those packages that are not yet
 in Guix.
+@item --mix-inputs
+@itemx -m
+Use vendored @code{mix-inputs} for @code{input} field instead of common
+dependencies.
+@end table
+
+@item mix
+@cindex mix
+Import metadata from the @file{mix.lock}, Elixir's
+@uref{https://hexdocs.pm/elixir/introduction-to-mix.html, mix} lock file,
+as in this example:
+
+@example
+guix import mix -f mix.lock yourapp
+@end example
+
+Mandatory options are:
+
+@table @code
+@item --lockfile=@var{file}
+@itemx -f @var{file}
+@option{--lockfile} must be a @file{mix.lock} file.
+
+@xref{Packaging Elixir Applications,,, guix-cookbook, GNU Guix Cookbook}, for
+packaging workflow utilizing it.
 @end table
 @end table
 
diff --git a/etc/teams.scm b/etc/teams.scm
index 6730a91008..ab21e6dfee 100755
--- a/etc/teams.scm
+++ b/etc/teams.scm
@@ -481,15 +481,21 @@ already exists.  Lookup team IDs among CURRENT-TEAMS."
         #:name "Erlang/Elixir/BEAM team"
         #:description "The virtual machine at the core of the Erlang Open
 Telecom Platform (OTP), Erlang and Elxir languages and packages, development
-of Rebar and Mix build systems and Hex.pm importer."
-        #:scope (list (make-regexp* "^gnu/packages/elixir(-.+|)\\.scm$")
+of Rebar and Mix build systems, Hex.pm and Mix importers."
+        #:scope (list (make-regexp* "^gnu/packages/beam-.+\\.scm$")
+                      (make-regexp* "^gnu/packages/elixir(-.+|)\\.scm$")
                       "guix/build/mix-build-system.scm"
                       "guix/build-system/mix.scm"
                       (make-regexp* "^gnu/packages/erlang(-.+|)\\.scm$")
                       "guix/build/rebar-build-system.scm"
                       "guix/build-system/rebar.scm"
                       "guix/import/hexpm.scm"
-                      "guix/scripts/import/hexpm.scm")))
+                      "guix/import/mix.scm"
+                      "guix/import/mix/mix-lock.scm"
+                      "guix/scripts/import/hexpm.scm"
+                      "guix/scripts/import/mix.scm"
+                      "tests/import/hexpm.scm"
+                      "tests/import/mix.scm")))
 
 (define-team bioinformatics
   (team 'bioinformatics
diff --git a/gnu/local.mk b/gnu/local.mk
index 60b1020b91..acbac89204 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -75,6 +75,7 @@
 # Copyright © 2025 Nigko Yerden <[email protected]>
 # Copyright © 2025 Cayetano Santos <[email protected]>
 # Copyright © 2025 bdunahu <[email protected]>
+# Copyright © 2025 Igorj Gorjaĉev <[email protected]>
 #
 # This file is part of GNU Guix.
 #
@@ -174,6 +175,9 @@ GNU_SYSTEM_MODULES =                                \
   %D%/packages/bash.scm                                \
   %D%/packages/batik.scm                       \
   %D%/packages/bdw-gc.scm                      \
+  %D%/packages/beam-apps.scm                   \
+  %D%/packages/beam-packages.scm               \
+  %D%/packages/beam-sources.scm                        \
   %D%/packages/benchmark.scm                   \
   %D%/packages/bioconductor.scm                        \
   %D%/packages/bioinformatics.scm              \
diff --git a/gnu/packages/beam-apps.scm b/gnu/packages/beam-apps.scm
new file mode 100644
index 0000000000..0fa7b96307
--- /dev/null
+++ b/gnu/packages/beam-apps.scm
@@ -0,0 +1,25 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (gnu packages beam-apps)
+  #:use-module ((guix licenses) #:prefix license:)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (guix download)
+  #:use-module (guix git-download)
+  #:use-module (guix build-system mix))
diff --git a/gnu/packages/beam-packages.scm b/gnu/packages/beam-packages.scm
new file mode 100644
index 0000000000..3144afb148
--- /dev/null
+++ b/gnu/packages/beam-packages.scm
@@ -0,0 +1,43 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (gnu packages beam-packages)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (guix download)
+  #:use-module (guix git-download)
+  #:use-module (guix build-system mix)
+  #:use-module (gnu packages beam-sources)
+  #:export (lookup-mix-inputs))
+
+;;;
+;;; This file is managed by ‘guix import’.  Do NOT add definitions manually.
+;;;
+;;; BEAM libraries fetched from hex.pm.
+;;;
+
+(define aaaa-separator 'begin-of-beam-packages)
+
+(define cccc-separator 'end-of-beam-packages)
+
+
+;;;
+;;; Mix inputs.
+;;;
+
+(define-mix-inputs lookup-mix-inputs)
diff --git a/gnu/packages/beam-sources.scm b/gnu/packages/beam-sources.scm
new file mode 100644
index 0000000000..efc2db9606
--- /dev/null
+++ b/gnu/packages/beam-sources.scm
@@ -0,0 +1,29 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (gnu packages beam-sources)
+  #:use-module (guix gexp)
+  #:use-module (guix packages)
+  #:use-module (guix download)
+  #:use-module (guix git-download)
+  #:use-module (guix build-system mix))
+
+;;;
+;;; BEAM libraries requiring modification.
+;;; These packages are hidden, as they are not interesting to users.
+;;;
diff --git a/guix/build-system/mix.scm b/guix/build-system/mix.scm
index 224f4ff94c..709fed017f 100644
--- a/guix/build-system/mix.scm
+++ b/guix/build-system/mix.scm
@@ -1,6 +1,7 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2023 Pierre-Henry Fröhring <[email protected]>
 ;;; Copyright © 2025 Giacomo Leidi <[email protected]>
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -28,7 +29,10 @@
   #:use-module (guix build mix-build-system)
   #:use-module (guix build-system gnu)
   #:use-module (guix build-system)
+  #:use-module (guix diagnostics)
+  #:use-module (guix download)
   #:use-module (guix gexp)
+  #:use-module (guix i18n)
   #:use-module (guix monads)
   #:use-module (guix packages)
   #:use-module (guix search-paths)
@@ -37,7 +41,12 @@
   #:use-module (ice-9 match)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
-  #:export (mix-build-system hexpm-uri))
+  #:export (mix-build-system
+            hexpm-uri
+            beam-name->package-name
+            hexpm-source
+            define-mix-inputs
+            mix-inputs))
 
 (define (default-elixir-hex)
   (@* (gnu packages elixir) elixir-hex))
@@ -64,6 +73,48 @@ See: 
https://github.com/hexpm/specifications/blob/main/endpoints.md";
     strip-prefix)
    name))
 
+(define (beam-name->package-name name)
+  (downstream-package-name "beam-" name))
+
+;; NOTE: Only use this procedure in (gnu packages beam-packages).
+(define* (hexpm-source package-name hexpm-name hexpm-version hexpm-hash
+                       #:key (patches '()) (snippet #f))
+  (origin
+    (method url-fetch)
+    (uri (hexpm-uri hexpm-name hexpm-version))
+    (file-name
+     (string-append "beam-" package-name "-" hexpm-version ".tar"))
+    (sha256 (base32 hexpm-hash))
+    (modules '((guix build utils)))
+    (patches patches)
+    (snippet snippet)))
+
+(define-syntax define-mix-inputs
+  (syntax-rules (=>)
+    ((_ lookup inputs ...)
+     (define lookup
+       (let ((table (make-hash-table)))
+         (letrec-syntax ((record
+                          (syntax-rules (=>)
+                            ((_) #t)
+                            ((_ (name => lst) rest (... ...))
+                             (begin
+                               (hashq-set! table 'name (filter identity lst))
+                               (record rest (... ...)))))))
+           (record inputs ...)
+           (lambda (name)
+             "Return the inputs for NAME."
+             (hashq-ref table name))))))))
+
+(define* (mix-inputs name #:key (module '(gnu packages beam-packages)))
+  "Lookup Mix inputs for NAME defined in MODULE, return an empty list if
+unavailable."
+  (let ((lookup (module-ref (resolve-interface module) 'lookup-mix-inputs)))
+    (or (lookup name)
+        (begin
+          (warning (G_ "no Mix inputs available for '~a'~%") name)
+          '()))))
+
 ;; A number of environment variables specific to the Mix build system are
 ;; reflected here.  They are documented at
 ;; https://hexdocs.pm/mix/Mix.html#module-environment-variables.  Other
@@ -75,6 +126,9 @@ See: 
https://github.com/hexpm/specifications/blob/main/endpoints.md";
                     source
                     (tests? #t)
                     (test-flags ''())
+                    (vendorize? #f)
+                    (vendor-symlinks? #t)
+                    (vendor-dir "guix-vendor")
                     (mix-path #f) ;See MIX_PATH.
                     (mix-exs "mix.exs") ;See MIX_EXS.
                     (build-per-environment #t) ;See :build_per_environment.
@@ -110,6 +164,9 @@ See: 
https://github.com/hexpm/specifications/blob/main/endpoints.md";
                            #:system #$system
                            #:tests? #$tests?
                            #:test-flags #$test-flags
+                           #:vendorize? #$vendorize?
+                           #:vendor-symlinks? #$vendor-symlinks?
+                           #:vendor-dir #$vendor-dir
                            #:mix-path #$mix-path
                            #:mix-exs #$mix-exs
                            #:mix-environments '#$mix-environments
diff --git a/guix/build/mix-build-system.scm b/guix/build/mix-build-system.scm
index f16fb1eaae..7a68b28559 100644
--- a/guix/build/mix-build-system.scm
+++ b/guix/build/mix-build-system.scm
@@ -1,6 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2023 Pierre-Henry Fröhring <[email protected]>
-;;; Copyright © 2024, 2026 Igorj Gorjaĉev <[email protected]>
+;;; Copyright © 2024-2026 Igorj Gorjaĉev <[email protected]>
 ;;; Copyright © 2024, 2025 Giacomo Leidi <[email protected]>
 ;;;
 ;;; This file is part of GNU Guix.
@@ -48,7 +48,13 @@
 VERSION are installed."
   (string-append path "/lib/elixir/" version))
 
-(define* (strip-prefix name #:optional (prefix "elixir-"))
+(define %elixir-prefix "elixir-")
+
+(define %beam-prefix "beam-")
+
+(define %vendorize-env-var "GUIX_MIX_VENDOR_DIR")
+
+(define* (strip-prefix name #:optional (prefix %elixir-prefix))
   "Return NAME without the prefix PREFIX."
   (if (string-prefix? prefix name)
       (string-drop name (string-length prefix))
@@ -83,9 +89,73 @@ working directory."
 (define (list-directories dir)
   "List absolute paths of directories directly under the directory DIR."
   (map (cute string-append dir "/" <>)
-       (scandir dir (lambda (filename)
-                      (and (not (member filename '("." "..")))
-                           (directory-exists? (string-append dir "/" 
filename)))))))
+       (scandir
+        dir (lambda (filename)
+              (and (not (member filename '("." "..")))
+                   (directory-exists? (string-append dir "/" filename)))))))
+
+(define* (unpack-vendorize #:key vendorize? vendor-dir inputs
+                           #:allow-other-keys)
+  "Unpack vendored packages."
+  (define (inputs->beam-inputs inputs)
+    "Filter using the label part from INPUTS."
+    (filter-map (lambda (input)
+                  (match input
+                    ((name . file)
+                     (and (beam-package? name) file))))
+                inputs))
+  (define (beam-input->upstream-name beam-input)
+    "Convert BEAM-INPUT into upstream package name."
+    ((compose
+      (cute package-name->elixir-name <> %beam-prefix)
+      (cute substring <> 33)
+      basename)
+     beam-input))
+  (define (copy-or-unpack-beam-package beam-input dep-dir)
+    "Copy contents, if BEAM-INPUT is a checkout package, otherwise unpack it."
+    (or (directory-exists? dep-dir)
+        (if (file-is-directory? beam-input)
+            (copy-recursively beam-input dep-dir)
+            (begin
+              (mkdir-p dep-dir)
+              (invoke "sh" "-c"
+                      (string-append "tar -xOf " beam-input
+                                     " contents.tar.gz"
+                                     " | tar -xz -C " dep-dir))))))
+  (and
+   vendorize?
+   (begin
+     (mkdir-p vendor-dir)
+     (setenv %vendorize-env-var (canonicalize-path vendor-dir))
+     (let ((beam-inputs (delete-duplicates (inputs->beam-inputs inputs))))
+       (unless (null? beam-inputs)
+         (for-each
+          (lambda (beam-input)
+            (let* ((upstream-name (beam-input->upstream-name beam-input))
+                   (dep-dir (string-append vendor-dir "/" upstream-name)))
+              (copy-or-unpack-beam-package beam-input dep-dir)))
+          beam-inputs))))))
+
+(define* (symlink-vendorize #:key vendorize? vendor-symlinks?
+                            #:allow-other-keys)
+  (and
+   vendorize?
+   vendor-symlinks?
+   (let* ((vendor-dir (getenv %vendorize-env-var))
+          (deps-dir (list-directories vendor-dir)))
+     (for-each
+      (lambda (dep-dir)
+        (let ((snapshot (string-contains dep-dir "_snapshot")))
+          (and
+           snapshot
+           (let ((canonical
+                  (string-drop-right
+                   dep-dir (- (string-length dep-dir) snapshot))))
+             (symlink dep-dir canonical)))))
+      deps-dir))))
+
+(define (beam-package? name)
+  (string-prefix? %beam-prefix name))
 
 (define* (set-mix-env #:key inputs mix-path mix-exs #:allow-other-keys)
   "Set environment variables.
@@ -113,22 +183,32 @@ See: 
https://hexdocs.pm/mix/1.15.7/Mix.html#module-environment-variables";
   (%elixir-version (elixir-version inputs))
   (format #t "Elixir version: ~a~%" (%elixir-version)))
 
-(define* (build #:key mix-environments #:allow-other-keys)
+
+(define* (build #:key mix-environments vendorize?
+                #:allow-other-keys)
   "Builds the Mix project."
   (for-each (lambda (mix-env)
               (setenv "MIX_ENV" mix-env)
-              (invoke "mix" "compile" "--no-deps-check"
-                      "--no-prune-code-paths"))
+              (if vendorize?
+                  (invoke "mix" "release")
+                  (invoke "mix" "compile" "--no-prune-code-paths"
+                          "--no-deps-check")))
             mix-environments))
 
-(define* (check #:key (tests? #t) (test-flags '()) #:allow-other-keys)
+(define* (check #:key (tests? #t) (test-flags '())
+                vendorize?
+                #:allow-other-keys)
   "Test the Mix project."
   (if tests?
-      (begin
+      (let ((maybe-no-deps-check-flag
+             (if vendorize? '() '("--no-deps-check"))))
         (setenv "MIX_ENV" "test")
-        (apply invoke "mix" "do" "compile" "--no-deps-check"
-                       "--no-prune-code-paths" "+" "test"
-                       "--no-deps-check" test-flags))
+        (apply invoke
+               (append '("mix" "do" "compile")
+                       maybe-no-deps-check-flag
+                       '("--no-prune-code-paths" "+" "test")
+                       maybe-no-deps-check-flag
+                       test-flags)))
       (format #t "tests? = ~a~%" tests?)))
 
 (define* (remove-mix-dirs . _)
@@ -137,7 +217,7 @@ 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->elixir-name name+ver)
+(define* (package-name->elixir-name name+ver #:optional (prefix 
%elixir-prefix))
   "Convert the Guix package NAME-VER to the corresponding Elixir name-version
 format.  Example: elixir-a-pkg-1.2.3 -> a_pkg or elixir-a-pkg-0.0.0-0.e51e36e
 -> a_pkg"
@@ -146,7 +226,7 @@ format.  Example: elixir-a-pkg-1.2.3 -> a_pkg or 
elixir-a-pkg-0.0.0-0.e51e36e
     (cute string-join <> "_")
     (cute drop-right <> (if git-version? 2 1))
     (cute string-split <> #\-))
-   (strip-prefix name+ver)))
+   (strip-prefix name+ver prefix)))
 
 (define* (install #:key
                   inputs
@@ -155,13 +235,24 @@ format.  Example: elixir-a-pkg-1.2.3 -> a_pkg or 
elixir-a-pkg-0.0.0-0.e51e36e
                   build-per-environment
                   #:allow-other-keys)
   "Install build artifacts in the store."
-  (let* ((lib-name (package-name->elixir-name name))
-         (lib-dir (string-append (elixir-libdir (assoc-ref outputs "out")) "/" 
lib-name))
-         (root (getenv "MIX_BUILD_ROOT"))
-         (env (if build-per-environment "prod" "shared")))
-    (mkdir-p lib-dir)
-    (copy-recursively (string-append (mix-build-dir root env) "/" lib-name) 
lib-dir
-                      #:follow-symlinks? #t)))
+  (if (beam-package? name)
+      (let* ((out (assoc-ref outputs "out"))
+             (excluded '("CHECKSUM" "contents.tar.gz" "environment-variables"
+                         "metadata.config" "VERSION"))
+             (files
+              (filter
+               (lambda (f)
+                 (not (member f (cons* "." ".." excluded))))
+               (scandir "."))))
+        (mkdir-p out)
+        (apply invoke "cp" "-r" (append files (list out))))
+      (let* ((lib-name (package-name->elixir-name name))
+             (lib-dir (string-append (elixir-libdir (assoc-ref outputs "out")) 
"/" lib-name))
+             (root (getenv "MIX_BUILD_ROOT"))
+             (env (if build-per-environment "prod" "shared")))
+        (mkdir-p lib-dir)
+        (copy-recursively (string-append (mix-build-dir root env) "/" 
lib-name) lib-dir
+                          #:follow-symlinks? #t))))
 
 (define %standard-phases
   (modify-phases gnu:%standard-phases
@@ -170,6 +261,8 @@ format.  Example: elixir-a-pkg-1.2.3 -> a_pkg or 
elixir-a-pkg-0.0.0-0.e51e36e
     (add-after 'install-locale 'set-mix-env set-mix-env)
     (add-after 'set-mix-env 'set-elixir-version set-elixir-version)
     (replace 'unpack unpack)
+    (add-after 'unpack 'unpack-vendorize unpack-vendorize)
+    (add-after 'unpack-vendorize 'symlink-vendorize symlink-vendorize)
     (replace 'build build)
     (replace 'check check)
     (add-before 'install 'remove-mix-dirs remove-mix-dirs)
diff --git a/guix/import/hexpm.scm b/guix/import/hexpm.scm
index 885e416fea..e0b2ac8de1 100644
--- a/guix/import/hexpm.scm
+++ b/guix/import/hexpm.scm
@@ -177,33 +177,37 @@ as symbols."
 (define* (make-hexpm-sexp #:key name version tarball-url
                           home-page synopsis description license
                           language build-system dependencies
+                          mix-inputs?
                           #:allow-other-keys)
   "Return the `package' s-expression for a hexpm package with the given NAME,
 VERSION, TARBALL-URL, HOME-PAGE, SYNOPSIS, DESCRIPTION, and LICENSE. The
 created package's name will stem from LANGUAGE. BUILD-SYSTEM defined the
-build-system, and DEPENDENCIES the inputs for the package."
-  (call-with-temporary-output-file
-   (lambda (temp port)
-     (and (url-fetch tarball-url temp)
-          (values
-       `(package
-         (name ,(hexpm-name->package-name name language))
-         (version ,version)
-         (source (origin
-                   (method url-fetch)
-                   (uri (hexpm-uri ,name version))
-                   (sha256 (base32 ,(guix-hash-url temp)))))
-         (build-system ,build-system)
-         ,@(maybe-inputs (dependencies->package-names dependencies) 'inputs)
-         (synopsis ,synopsis)
-         (description ,(beautify-description description))
-         (home-page ,(match home-page
-                            (() "")
-                            (_ home-page)))
-         (license ,(match license
-                          (() #f)
-                          ((license) license)
-                          (_ `(list ,@license))))))))))
+build-system, and DEPENDENCIES or MIX-INPUTS? the inputs for the package."
+  (let ((package-name (hexpm-name->package-name name language)))
+    (call-with-temporary-output-file
+     (lambda (temp port)
+       (and (url-fetch tarball-url temp)
+            (values
+             `(package
+                (name ,package-name)
+                (version ,version)
+                (source (origin
+                          (method url-fetch)
+                          (uri (hexpm-uri ,name version))
+                          (sha256 (base32 ,(guix-hash-url temp)))))
+                (build-system ,build-system)
+                ,@(if mix-inputs?
+                      `((inputs (mix-inputs ',(string->symbol package-name))))
+                      (maybe-inputs (dependencies->package-names dependencies) 
'inputs))
+                (synopsis ,synopsis)
+                (description ,(beautify-description description))
+                (home-page ,(match home-page
+                              (() "")
+                              (_ home-page)))
+                (license ,(match license
+                            (() #f)
+                            ((license) license)
+                            (_ `(list ,@license)))))))))))
 
 (define (strings->licenses strings)
   "Convert the list of STRINGS into a list of license objects."
@@ -221,11 +225,13 @@ object, ordered from newest to oldest."
   (sort (map hexpm-version-number (hexpm-versions package))
         version>?))
 
-(define* (hexpm->guix-package package-name #:key version #:allow-other-keys)
-  "Fetch the metadata for PACKAGE-NAME from hexpms.io, and return the
+(define* (hexpm->guix-package package-name #:key version
+                              mix-inputs? #:allow-other-keys)
+  "Fetch the metadata for PACKAGE-NAME from hex.pm, and return the
 `package' s-expression corresponding to that package, or #f on failure.
 When VERSION is specified, attempt to fetch that version; otherwise fetch the
-latest version of PACKAGE-NAME."
+latest version of PACKAGE-NAME.  It can use `inputs` with `mix-inputs` when
+MIX-INPUTS? is specified."
 
   (define package
     (lookup-hexpm package-name))
@@ -277,6 +283,7 @@ latest version of PACKAGE-NAME."
            #:name package-name
            #:version version-number
            #:dependencies dependencies
+           #:mix-inputs? mix-inputs?
            #:home-page (or (and (not (eq? docs-html-url 'null))
                                 docs-html-url)
                            ;; TODO: Homepage?
diff --git a/guix/import/mix.scm b/guix/import/mix.scm
new file mode 100644
index 0000000000..89a6e6fa4e
--- /dev/null
+++ b/guix/import/mix.scm
@@ -0,0 +1,143 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (guix import mix)
+  #:use-module (guix import mix mix-lock)
+  #:use-module (guix base16)
+  #:use-module (guix base32)
+  #:use-module (guix read-print)
+  #:use-module (guix scripts download)
+  #:autoload   (guix utils) (find-definition-location)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 textual-ports)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-71)
+  #:use-module (guix build-system mix)
+  #:export (mix-lock->expressions
+            mix-inputs-from-lockfile
+            find-mix-inputs-location
+            extract-mix-inputs))
+
+
+;;;
+;;; Convert ‘mix.lock’ to Guix sources.
+;;;
+
+(define (mix-lock->expressions lockfile package-name)
+  "Given LOCKFILE, a 'mix.lock' file, import its content as source
+expressions.  Return a source list and a mix inputs entry for PACKAGE-NAME
+referencing all imported sources."
+  (define (mix-lock-entry->guix-source mix-lock-entry)
+    (match mix-lock-entry
+      (('mix-lock-entry
+        ('entry-name entry-name)
+        ('entry-hexpm
+         ('hexpm-name hexpm-name)
+         ('hexpm-version hexpm-version)
+         _
+         ('hexpm-checksum hexpm-checksum)))
+       `(define
+          ,(string->symbol
+            (string-append
+             (beam-name->package-name entry-name) "-" hexpm-version))
+          (hexpm-source ,entry-name ,hexpm-name ,hexpm-version
+                        ,(bytevector->nix-base32-string
+                          (base16-string->bytevector hexpm-checksum)))))
+      ;; Git snapshot.
+      (('mix-lock-entry
+        ('entry-name entry-name)
+        ('entry-git
+         ('git-url git-url)
+         ('git-commit git-commit)))
+       (begin
+         (let* ((version (string-append "snapshot." (string-take git-commit 
7)))
+                (checksum
+                 (second
+                  (string-split
+                   (with-output-to-string
+                     (lambda _
+                       (guix-download "-g" git-url
+                                      (string-append "--commit=" git-commit))))
+                   #\newline))))
+           `(define
+              ,(string->symbol
+                (string-append
+                 (beam-name->package-name entry-name) "-" version))
+              (origin
+                (method git-fetch)
+                (uri (git-reference
+                       (url ,git-url)
+                       (commit ,git-commit)))
+                (file-name
+                 (git-file-name
+                  ,(beam-name->package-name entry-name) ,version))
+                (sha256 (base32 ,checksum)))))))
+      (else #f)))
+
+  (let* ((source-expressions
+          (filter-map mix-lock-entry->guix-source
+                      (mix-lock-string->scm
+                       (call-with-input-file lockfile get-string-all))))
+         (beam-inputs-entry
+          `(,(string->symbol package-name) =>
+            (list ,@(map second source-expressions)))))
+    (values source-expressions beam-inputs-entry)))
+
+(define* (mix-inputs-from-lockfile #:optional (lockfile "mix.lock"))
+  "Given LOCKFILE (default to \"mix.lock\" in current directory), return a
+source list imported from it, to be used as package inputs.  This procedure
+can be used for adding a manifest file within the source tree of a BEAM
+application."
+  (let ((source-expressions
+         mix-inputs-entry
+         (mix-lock->expressions lockfile "mix-inputs-temporary")))
+    (eval-string
+     (call-with-output-string
+       (lambda (port)
+         (for-each
+          (cut pretty-print-with-comments port <>)
+          `((use-modules (guix build-system mix))
+            ,@source-expressions
+            (define-mix-inputs lookup-mix-inputs ,mix-inputs-entry)
+            (lookup-mix-inputs 'mix-inputs-temporary))))))))
+
+(define (find-mix-inputs-location file)
+  "Search in FILE for a top-level definition of mix inputs.  Return the
+location if found, or #f otherwise."
+  (find-definition-location file 'lookup-mix-inputs
+                            #:define-prefix 'define-mix-inputs))
+
+(define* (extract-mix-inputs file #:key exclude)
+  "Search in FILE for a top-level definition of mix inputs.  If found,
+return its entries excluding EXCLUDE, or an empty list otherwise."
+  (call-with-input-file file
+    (lambda (port)
+      (do ((syntax (read-syntax port)
+                   (read-syntax port)))
+          ((match (syntax->datum syntax)
+             (('define-mix-inputs 'lookup-mix-inputs _ ...) #t)
+             ((? eof-object?) #t)
+             (_ #f))
+           (or (and (not (eof-object? syntax))
+                    (match (syntax->datum syntax)
+                      (('define-mix-inputs 'lookup-mix-inputs inputs ...)
+                       (remove (lambda (mix-input-entry)
+                                 (eq? exclude (first mix-input-entry)))
+                               inputs))))
+               '()))))))
diff --git a/guix/import/mix/mix-lock.scm b/guix/import/mix/mix-lock.scm
new file mode 100644
index 0000000000..4d2c9ecec5
--- /dev/null
+++ b/guix/import/mix/mix-lock.scm
@@ -0,0 +1,201 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (guix import mix mix-lock)
+  #:use-module (ice-9 peg)
+  #:export (mix-lock-string->scm
+
+            entry-git
+            entry-hexpm
+            entry-name
+            git-commit
+            git-url
+            hexpm-build-system
+            hexpm-build-systems
+            hexpm-name
+            hexpm-version
+            mix-lock
+            mix-lock-entry))
+
+;;;
+;;; PEG parser for ‘mix.lock’.
+;;;
+
+(define (mix-lock-string->scm str)
+  (peg:tree (search-for-pattern mix-lock str)))
+
+;; Auxiliar peg patterns
+(define-peg-pattern numeric-char body
+  (range #\0 #\9))
+
+(define-peg-pattern lowercase-char body
+  (range #\a #\z))
+
+(define-peg-pattern uppercase-char body
+  (range #\A #\Z))
+
+(define-peg-pattern alphabetic-char body
+  (or lowercase-char uppercase-char))
+
+(define-peg-pattern alphanumeric-char body
+  (or alphabetic-char numeric-char))
+
+(define-peg-pattern space-char body
+  (+ (or " " "\t" "\n" "\r")))
+
+(define-peg-pattern opt-space-char body
+  (* space-char))
+
+;; string: "string"
+(define-peg-pattern string-char body
+  (or alphanumeric-char
+      space-char
+      "_" "." "~" "=" "<" ">" "/" ":" "-"))
+
+(define-peg-pattern string body
+  (and (ignore "\"")
+       (* string-char)
+       (ignore "\"")))
+
+;; atom: :hex or true | false | nil
+(define-peg-pattern atom-char body
+  (or alphanumeric-char "_" "@" "?" "!"))
+
+(define-peg-pattern atom body
+  (or
+   (and (ignore ":")
+        (+ atom-char))
+   "true" "false" "nil"))
+
+;; list: [ ... ]
+(define-peg-pattern list body
+  (and (ignore "[")
+       (* (and
+           (ignore (? ", "))
+           (or value key-value)))
+       (ignore "]")))
+
+;; tuple: { ... }
+(define-peg-pattern tuple body
+  (and (ignore "{")
+       (* (and
+           (ignore (? ", "))
+           value))
+       (ignore "}")
+       (ignore (? "\n"))))
+
+;; syntactic sugar for [{key, value} ...]
+(define-peg-pattern key-value body
+  (ignore
+   (and (+ atom-char) ": " value)))
+
+;; value may be string, atom, tuple, list
+(define-peg-pattern value body
+  (and (ignore opt-space-char)
+       (or string atom tuple list)
+       (ignore opt-space-char)))
+
+;; ignore several constructions
+(define-peg-pattern ignore-string body
+  (ignore
+   (and "\""
+        (* (or string-char
+               space-char))
+        "\"")))
+
+(define-peg-pattern ignore-comma-space body
+  (ignore ", "))
+
+(define-peg-pattern ignore-list body
+  (ignore (and "["
+               (* (and
+                   (? ", ")
+                   (or value key-value)))
+               "]")))
+
+;; exported symbols
+(define-peg-pattern entry-name all
+  string)
+
+(define-peg-pattern hexpm-name all
+  atom)
+
+(define-peg-pattern hexpm-version all
+  string)
+
+(define-peg-pattern hexpm-checksum all
+  string)
+
+(define-peg-pattern hexpm-build-system all
+  (capture (+ (or alphanumeric-char "_" "-"))))
+
+(define-peg-pattern hexpm-build-systems all
+  (and (ignore "[")
+       (capture
+        (* (and (ignore ":")
+                (capture hexpm-build-system)
+                (ignore (? ", ")))))
+       (ignore "]")))
+
+(define-peg-pattern git-url all
+  string)
+
+(define-peg-pattern git-commit all
+  string)
+
+(define-peg-pattern entry-hexpm all
+  (and
+   (ignore ": {:hex, ")
+   hexpm-name
+   ignore-comma-space
+   (capture hexpm-version)
+   ignore-comma-space
+   ignore-string
+   ignore-comma-space
+   (capture hexpm-build-systems)
+   ignore-comma-space
+   ignore-list
+   ignore-comma-space
+   ignore-string
+   ignore-comma-space
+   (capture hexpm-checksum)
+   (ignore "}")))
+
+(define-peg-pattern entry-git all
+  (and
+   (ignore ": {:git, ")
+   git-url
+   ignore-comma-space
+   git-commit
+   ignore-comma-space
+   ignore-list
+   (ignore "}")))
+
+(define-peg-pattern mix-lock-entry all
+  (and (ignore (? "  "))
+       (capture entry-name)
+       (or
+        entry-hexpm
+        entry-git)
+       (ignore (? ",\n"))))
+
+;; mix.lock
+(define-peg-pattern mix-lock all
+  (and (ignore "%{\n")
+       (* mix-lock-entry)
+       (ignore "}")))
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm
index 464fa372cd..ef33ef5f40 100644
--- a/guix/scripts/import.scm
+++ b/guix/scripts/import.scm
@@ -7,6 +7,7 @@
 ;;; Copyright © 2021 Xinglu Chen <[email protected]>
 ;;; Copyright © 2022 Philip McGrath <[email protected]>
 ;;; Copyright © 2024 Herman Rimm <[email protected]>
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -63,6 +64,7 @@
 (define importers '("composer" "cpan" "cran" "crate" "egg" "elm" "elpa"
                     "gem" "gnu" "go" "hackage" "hexpm" "json" "luanti"
                     "minetest"          ; deprecated
+                    "mix"
                     "npm-binary" "nuget" "opam" "pypi" "stackage" "texlive"))
 
 (define (resolve-importer name)
@@ -143,10 +145,10 @@ PROC callback."
     ((or ("-i" file importer args ...)
          ("--insert" file importer args ...))
      (let* ((define-prefixes
-             `(,@(if (member importer '("crate"))
-                     '(define)
-                     '())
-               define-public))
+              `(,@(if (member importer '("crate" "mix"))
+                      '(define)
+                      '())
+                define-public))
             (define-prefix? (cut member <> define-prefixes))
             (find-and-insert
              (lambda (expr)
@@ -172,9 +174,9 @@ PROC callback."
     ((importer args ...)
      (let ((print (lambda (expr)
                     (leave-on-EPIPE
-                      (pretty-print-with-comments
-                        (current-output-port) expr)
-                      ;; Two newlines: one after the closing paren, and
-                      ;; one to leave a blank line.
-                      (newline) (newline)))))
+                     (pretty-print-with-comments
+                      (current-output-port) expr)
+                     ;; Two newlines: one after the closing paren, and
+                     ;; one to leave a blank line.
+                     (newline) (newline)))))
        (import-as-definitions importer args print)))))
diff --git a/guix/scripts/import/hexpm.scm b/guix/scripts/import/hexpm.scm
index eb9a1b0af5..c645273129 100644
--- a/guix/scripts/import/hexpm.scm
+++ b/guix/scripts/import/hexpm.scm
@@ -47,6 +47,8 @@ Import and convert the hex.pm package for PACKAGE-NAME.\n"))
   (newline)
   (display (G_ "
   -r, --recursive        import packages recursively"))
+  (display (G_ "
+  -m, --mix-inputs       use mix-inputs for input field"))
   (newline)
   (show-bug-report-information))
 
@@ -62,6 +64,9 @@ Import and convert the hex.pm package for PACKAGE-NAME.\n"))
          (option '(#\r "recursive") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'recursive #t result)))
+         (option '("mix-inputs") #f #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'mix-inputs #t result)))
          %standard-import-options))
 
 
@@ -94,7 +99,9 @@ Import and convert the hex.pm package for PACKAGE-NAME.\n"))
                      (_ #f))
                     (hexpm-recursive-import name version))
                ;; Single import
-               (let ((sexp (hexpm->guix-package name #:version version)))
+               (let* ((mix-inputs (assoc-ref opts 'mix-inputs))
+                      (sexp (hexpm->guix-package name #:version version
+                                                 #:mix-inputs? mix-inputs)))
                  (unless sexp
                    (leave (G_ "failed to download meta-data for package 
'~a'~%")
                           spec))
diff --git a/guix/scripts/import/mix.scm b/guix/scripts/import/mix.scm
new file mode 100644
index 0000000000..476ff9de48
--- /dev/null
+++ b/guix/scripts/import/mix.scm
@@ -0,0 +1,122 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (guix scripts import mix)
+  #:use-module (guix ui)
+  #:use-module (guix utils)
+  #:use-module (guix read-print)
+  #:use-module (guix scripts)
+  #:use-module (guix import mix)
+  #:use-module (guix scripts import)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-37)
+  #:use-module (srfi srfi-71)
+  #:use-module (ice-9 match)
+  #:export (guix-import-mix))
+
+
+;;;
+;;; Command-line options.
+;;;
+
+(define %default-options
+  '())
+
+(define (show-help)
+  (display (G_ "Usage: guix import mix PACKAGE-NAME
+Import package dependencies from its 'mix.lock'.\n"))
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (display (G_ "
+  -f, --lockfile=FILE    import dependencies from FILE, a 'mix.lock' file"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  ;; Specification of the command-line options.
+  (cons* (option '(#\h "help") #f #f
+                 (lambda args
+                   (show-help)
+                   (exit 0)))
+         (option '(#\V "version") #f #f
+                 (lambda args
+                   (show-version-and-exit "guix import mix")))
+         (option '(#\f "lockfile") #f #t
+                 (lambda (opt name arg result)
+                   (if (file-exists? arg)
+                       (alist-cons 'lockfile arg result)
+                       (leave (G_ "file '~a' does not exist~%") arg))))
+         %standard-import-options))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (guix-import-mix . args)
+  (define (parse-options)
+    ;; Return the alist of option values.
+    (parse-command-line args %options (list %default-options)
+                        #:build-options? #f))
+
+  (let* ((opts (parse-options))
+         (lockfile (assoc-ref opts 'lockfile))
+         (file-to-insert (assoc-ref opts 'file-to-insert))
+         (args (filter-map (match-lambda
+                             (('argument . value)
+                              value)
+                             (_ #f))
+                           (reverse opts))))
+    (match args
+      ((spec)
+       (define-values (name version)
+         (package-name->name+version spec))
+
+       (if lockfile
+           (let ((source-expressions
+                  _
+                  (mix-lock->expressions lockfile name)))
+             (when file-to-insert
+               (let* ((source-expressions
+                       mix-inputs-entry
+                       (mix-lock->expressions lockfile name))
+                      (term (first mix-inputs-entry))
+                      (mix-inputs
+                       `(define-mix-inputs lookup-mix-inputs
+                          ,@(sort
+                             (cons mix-inputs-entry
+                                   (extract-mix-inputs
+                                    file-to-insert #:exclude term))
+                             (lambda (a b)
+                               (string< (symbol->string (first a))
+                                        (symbol->string (first b)))))))
+                      (_
+                       (and=> (find-mix-inputs-location file-to-insert)
+                              delete-expression))
+                      (port (open-file file-to-insert "a")))
+                 (pretty-print-with-comments port mix-inputs)
+                 (newline port)
+                 (close-port port)))
+             source-expressions)))
+      (()
+       (leave (G_ "too few arguments~%")))
+      ((many ...)
+       (leave (G_ "too many arguments~%"))))))
diff --git a/tests/import/hexpm.scm b/tests/import/hexpm.scm
index 1e746f9b34..e99eb0e394 100644
--- a/tests/import/hexpm.scm
+++ b/tests/import/hexpm.scm
@@ -250,4 +250,48 @@
           (x
            (pk 'fail x #f))))))
 
+(test-assert "hexpm-with-mix-inputs"
+  ;; Replace network resources with sample data.
+  (mock ((guix http-client) http-fetch
+         (lambda (url . rest)
+           (match url
+             ("https://hex.pm/api/packages/bla";
+              (values (open-input-string test-bla-package)
+                      (string-length test-bla-package)))
+             ("https://hex.pm/api/packages/bla/releases/1.5.0";
+              (values (open-input-string test-bla-release)
+                      (string-length test-bla-release)))
+             (_ (error "http-fetch got unexpected URL: " url)))))
+  (mock ((guix build download) url-fetch
+         (lambda* (url file-name
+                       #:key
+                       (mirrors '()) verify-certificate?)
+           (with-output-to-file file-name
+             (lambda ()
+               (display
+                (match url
+                  ("https://repo.hex.pm/tarballs/bla-1.5.0.tar";
+                   "source")
+                  (_ (error "url-fetch got unexpected URL: " url))))))))
+    (match (hexpm->guix-package "bla" #:mix-inputs? #t)
+      (`(package
+          (name "erlang-bla")
+          (version "1.5.0")
+          (source
+           (origin
+             (method url-fetch)
+             (uri (hexpm-uri "bla" version))
+             (sha256
+              (base32
+               "0zcl4dgcmqwl1g5xb901pd6dz61r1xgmac9mqlwvh022paa6gks1"))))
+          (build-system rebar-build-system)
+          (inputs (mix-inputs 'erlang-bla))
+          (synopsis "A cool package")
+          (description "This package provides a cool package.")
+          (home-page "https://hex.pm/packages/bla";)
+          (license (list license:expat license:asl2.0)))
+       #t)
+      (x
+       (pk 'fail x #f))))))
+
 (test-end "hexpm")
diff --git a/tests/import/mix.scm b/tests/import/mix.scm
new file mode 100644
index 0000000000..46d33165a3
--- /dev/null
+++ b/tests/import/mix.scm
@@ -0,0 +1,97 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2025 Igorj Gorjaĉev <[email protected]>
+;;;
+;;; 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/>.
+
+(define-module (test-mix)
+  #:use-module (guix import mix)
+  #:use-module (guix base32)
+  #:use-module (guix build-system mix)
+  #:use-module (gcrypt hash)
+  #:use-module (guix tests)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-64)
+  #:use-module (ice-9 binary-ports)
+  #:use-module (ice-9 match))
+
+(define test-mix-lock-file
+  "\
+%{
+  \"gen_stage\": {:hex, :gen_stage, \"1.3.2\", \
+\"7c77e5d1e97de2c6c2f78f306f463bca64bf2f4c3cdd606affc0100b89743b7b\", \
+[:mix], [], \"hexpm\", \
+\"0ffae547fa777b3ed889a6b9e1e64566217413d018cabd825f786e843ffe63e7\"},
+  \"eini\": {:hex, :eini_beam, \"2.2.4\", \
+\"02143b1dce4dda4243248e7d9b3d8274b8d9f5a666445e3d868e2cce79e4ff22\", \
+[:rebar3], [], \"hexpm\", \
+\"12de479d144b19e09bb92ba202a7ea716739929afdf9dff01ad802e2b1508471\"},
+  \"erlydtl\": {:git, \"https://github.com/manuel-rubio/erlydtl.git\";, \
+\"dffa1a73ee2bfba14195b8b3964c39f007ff1284\", []},
+}")
+
+(define temp-file
+  (string-append "t-mix-" (number->string (getpid))))
+
+(test-begin "mix")
+
+(test-assert "mix-lock-file-import"
+  (begin
+    (call-with-output-file temp-file
+      (lambda (port)
+        (display test-mix-lock-file port)))
+    (mock
+     ((guix scripts download) guix-download
+      (lambda _
+        (format #t "~a~%~a~%"
+                "/gnu/store/m43vixiijc26ni5p9zvbvjrs311h4fsm-erlydtl-dffa1a7"
+                "1jhcfh0idadlh9999kjzx1riqjw0k05wm6ii08xkjvirhjg0vawh")))
+     (let-values
+         (((source-expressions beam-inputs-entry)
+           (mix-lock->expressions temp-file "test")))
+       (and
+        (match source-expressions
+          ('((define beam-gen-stage-1.3.2
+               (hexpm-source
+                "gen_stage" "gen_stage" "1.3.2"
+                "1rv3zqzq8vkqby1bvjhqs09p88b68pkf3fd6i7c3wyvpz93ybyhg"))
+             (define beam-eini-2.2.4
+               (hexpm-source
+                "eini" "eini_beam" "2.2.4"
+                "0wc4a2qy40nq3bqdzygxka93jrvixakh58ibp6dy06ab2jflgphj"))
+             (define beam-erlydtl-snapshot.dffa1a7
+               (origin
+                 (method git-fetch)
+                 (uri (git-reference
+                        (url "https://github.com/manuel-rubio/erlydtl.git";)
+                        (commit "dffa1a73ee2bfba14195b8b3964c39f007ff1284")))
+                 (file-name (git-file-name "beam-erlydtl" "snapshot.dffa1a7"))
+                 (sha256
+                  (base32
+                   "1jhcfh0idadlh9999kjzx1riqjw0k05wm6ii08xkjvirhjg0vawh")))))
+           #t)
+          (x
+           (pk 'fail (pretty-print-with-comments (current-output-port) x) #f)))
+        (match beam-inputs-entry
+          (`(test => (list beam-gen-stage-1.3.2
+                           beam-eini-2.2.4
+                           beam-erlydtl-snapshot.dffa1a7))
+           #t)
+          (x
+           (pk 'fail x #f))))))))
+
+(test-end "mix")
+
+(false-if-exception (delete-file temp-file))

Reply via email to