Illustration
They are multiple ways to integrate Elixir and Guix. This article explores a way using the Application Resource File.
  1. The objective is identical to the Guix and Elixir article.
  2. In addition, propagated-inputs should not be used in Guix packages definitions.

The result is identical to the Guix and Elixir article.

The experiment is identical to the Guix and Elixir article.

  1. If elixir-pkg is a Guix Elixir package such that:
    1. it depends on a Guix Erlang package ;
      • There is an Erlang package in the inputs field of the package.
    2. it provides executables ;
    3. it provides an application.
  2. then: guix shell --container elixir elixir-pkg should install the application and executables in the environment.
    • If it provides an executable executable, then $ executable calls it.
    • If it provides a module Mod, then: $ iex; $ {:module, Mod} = Code.ensure_loaded(Mod)
    • If it provides an application App, then: $ iex; $ {:ok, _} = Application.ensure_all_started(App)

test_project is an Elixir application that depends on telemetry, an Erlang application. It provides an executable test_project-bin that calls the telemetry application and prints "Success."

  1. Instead of experimenting with Phoenix, we build a simpler Elixir application that depends on an Erlang package.
  2. The Elixir application depends on telemetry, a Rebar package that is also a dependency of Phoenix.
  3. The experiment can be downloaded as an archive here.

The application directory is:

test_project/ ├── lib │   └── application.ex ├── mix.exs ├── README.md └── test ├── test_helper.exs └── test_project_test.exs

mix.exs is:

defmodule TestProject.MixProject do use Mix.Project def project do [ app: :test_project, version: "0.1.0", elixir: "~> 1.16", start_permanent: Mix.env() == :prod, deps: [{:telemetry, "~> 1.2"}] ] end def application do [ extra_applications: [:logger], mod: {TestProject, []} ] end end

which means — among other things — that the application named :test_project has :telemetry as a dependency. application.ex content is:

defmodule TestProject do require Logger use Application def start(_type, _args) do event_handler_id = "test-project-instrument" handler = &__MODULE__.handle_event/4 event = [:hello] :telemetry.attach_many(event_handler_id, [event], handler, nil) Supervisor.start_link([], strategy: :one_for_one) end def handle_event([:hello], measurements, _metadata, _config) do Logger.info("[Name telemetry: #{measurements.name}]") end def hello(name) do :telemetry.execute([:hello], %{name: name}, %{}) "Hello #{name}!" end end

which means that:

  1. After the application has started, the :telemetry application will invoke handle_event after receiving a :hello event.
  2. As a consequence, the string [Name telemetry: #{measurements.name}] will be logged.

TestProject.hello("name") has two effects:

  1. It sends a :hello message to the :telemetry application.
  2. It returns the strings "Hello name!".

Since the application model depends on :telemetry, then: a erlang-telemetry package is needed and will be used as a dependency of the application model Guix package definition.

(define erlang-telemetry (package (name "erlang-telemetry") (version "1.2.1") (source (origin (method url-fetch) (uri (hexpm-uri "telemetry" version)) (sha256 (base32 "1mgyx9zw92g6w8fp9pblm3b0bghwxwwcbslrixq23ipzisfwxnfs")))) (build-system rebar-build-system) (synopsis "Dynamic dispatching library for metrics and instrumentation") (description "Dynamic dispatching library for metrics and instrumentation.") (home-page "https://hexdocs.pm/telemetry/") (license license:asl2.0)))

A Makefile builds an archive _build/test_project.tar.gz out of the test_project/. This archive will serve as the source field of the application model Guix package.

The application model has an associated Guix package definition:

(use-modules (guix packages) (guix download) (guix build-system mix) (guix build-system rebar) (guix gexp) ((guix licenses) #:prefix license:)) (define erlang-telemetry …) (package (name "elixir-test-project") (version "0") (source (local-file (canonicalize-path "_build/test_project.tar.gz"))) (inputs (list erlang-telemetry)) (build-system mix-build-system) (synopsis "Test project package") (description "Test project package.") (home-page "https://example.com") (license #f))

The Guix mix build system is insufficient since trying to build the application model package gives:

$ guix build -K -f test-project-package.scm … ** (Mix) Could not start application telemetry: could not find application file: telemetry.app …

Even if the telemetry package is in the store:

$ tree /gnu/store/46h2sk93wa17jcshydkxr6r9ws83by5a-erlang-telemetry-1.2.1/ ├── lib │   └── erlang │   └── lib │   └── telemetry │   └── ebin │   ├── telemetry.app │   ├── telemetry_app.beam │   ├── telemetry.beam │   ├── telemetry_handler_table.beam │   ├── telemetry_sup.beam │   └── telemetry_test.beam └── share └── doc └── erlang-telemetry-1.2.1 └── LICENSE

There is a mix build system implementation such that test-project-package.scm finishes successfully. If an Erlang or Elixir dependencies is declared in the inputs field and in the Application Resource File of the application, then: it is a runtime dependency and should exist in the store but not necessarily in the profile. Succintly, an implemetation would look like so:

  1. guix pkgsuccessful build
    1. guix pkgruntime dependencies
      1. guix pkginputs
      2. guix pkg.appapplications
      3. inputs, applicationsruntime dependencies
    2. runtime dependencies, storesuccessful build
      1. runtime dependencies, storestore'
      2. guix pkgbin
      3. runtime dependencies, ERL_LIBS, binbin'
      4. store', bin'successful build

Given the application model package, its runtime dependencies (i.e. telemetry), can we build an environment where the package application can be executed without error?

  1. An environment is built: $ guix shell -C -D -f test-project-package.scm --preserve=LC_ALL
  2. The telemetry package exists: [env]$ echo /gnu/store/* | tr ' ' '\n' | grep telemetry /gnu/store/k2blpmzb8z15idsppifq1dhkxhwpgh0k-erlang-telemetry-1.2.1
  3. The application is compiled: [env]$ pushd src/test_project [env]$ MIX_ENV=prod mix compile --no-deps-check [env]$ tree _build/prod/ _build/prod/ └── lib └── test_project ├── consolidated │   ├── Elixir.Collectable.beam │   ├── Elixir.Enumerable.beam │   ├── Elixir.IEx.Info.beam │   ├── Elixir.Inspect.beam │   ├── Elixir.List.Chars.beam │   └── Elixir.String.Chars.beam └── ebin ├── Elixir.TestProject.beam └── test_project.app
  4. The test_project.app Application Resource File lists telemetry in its applications field. So: \text{inputs} \cap \text{applications} = \{ \text{telemetry} \} {application,test_project, [{config_mtime,1723125848}, {optional_applications,[]}, {applications,[kernel,stdlib,elixir,logger,telemetry]}, {description,"test_project"}, {modules,['Elixir.TestProject']}, {registered,[]}, {vsn,"0.1.0"}, {mod,{'Elixir.TestProject',[]}}]}.
  5. A way for the OS to tell an Erlang system about the existence of Erlang applications in the file system is through then ERL_LIBS environment variable: export ERL_LIBS=/gnu/store/k2blpmzb8z15idsppifq1dhkxhwpgh0k-erlang-telemetry-1.2.1/lib/erlang/lib export ERL_LIBS=${ERL_LIBS}:$PWD/src/test_project/_build/prod/lib
  6. The Application Model executes as expected: [env]$ iex iex(1)> Application.ensure_all_started(:test_project) {:ok, [:telemetry, :test_project]} iex(2)> TestProject.hello("Joe") 10:57:38.531 [info] [Name telemetry: Joe] "Hello Joe!"
  1. Given the Application Resource File, an Erlang script read_applications.erl can read the runtime dependencies: -module(read_applications). -export([main/1]). main([FileName]) -> {ok,ApplicationSpecification} = file:consult(FileName), [{application, _AppName, AppData}] = ApplicationSpecification, {applications, Applications} = lists:keyfind(applications, 1, AppData), io:format("~p~n", [Applications]). [env]$ erlc read_applications.erl [env]$ erl -noshell -s read_applications main "test_project.app" -s init stop [kernel,stdlib,elixir,logger,telemetry]
  2. Given the script below, Guile reads the applications field into a list of strings. (use-modules (rnrs io ports)) (define compilation-exit-status (system* "erlc" "read_applications.erl")) (when (not (eq? compilation-exit-status 0)) (throw 'compilation-failed "erlc failed to compile read_applications.erl")) (define (read_applications application_resource_file) (let* ((input+output (pipe)) (pid (spawn "erl" `("erl" "-noshell" "-s" "read_applications" "main" ,application_resource_file "-s" "init" "stop") #:output (cdr input+output))) (output "")) (waitpid pid) (close-port (cdr input+output)) (set! output (get-string-all (car input+output))) (close-port (car input+output)) output)) (define (list-of-strings input) (let* ((trimmed (string-trim-both input #\space)) (no-brackets (substring trimmed 1 (- (string-length trimmed) 2))) (elements (string-split no-brackets #\,)) (result (map string-trim elements))) result)) (format #t "~s~%" (list-of-strings (read_applications "test_project.app"))) [env]$ guile read_applications.scm ("kernel" "stdlib" "elixir" "logger" "telemetry")
  3. Given a package, it is possible to deduce the runtime dependencies.
    1. A Guix package lists its dependencies in the inputs field.
    2. Each item in inputs is a package
    3. Each package has a name, for instance erlang-name or elixir-name or someting else.
    4. if name is also in applications, then: its is a runtime dependency.
    5. All the runtime dependencies form the runtime dependencies.
  1. Given the runtime package dependencies, there are installed in the store.
    • (run-with-store (package->derivation runtime-dep)) should intall in the store the package runtime-dep taken from the runtime dependencies.

The mix build system has integrated the steps above. Compute the transitive closure of dependencies. See: .erlang start-up file