- The objective is identical to the Guix and Elixir article.
- 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.
- If
elixir-pkg
is a Guix Elixir package such that:- it depends on a Guix Erlang package ;
- There is an Erlang package in the
inputs
field of the package.
- There is an Erlang package in the
- it provides executables ;
- it provides an application.
- it depends on a Guix Erlang package ;
- 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)
- If it provides an executable
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."
- Instead of experimenting with Phoenix, we build a simpler Elixir application that depends on an Erlang package.
- The Elixir application depends on
telemetry
, a Rebar package that is also a dependency of Phoenix. - The experiment can be downloaded as an archive here.
The application directory is:
mix.exs
is:
which means — among other things — that the application named :test_project
has :telemetry
as a dependency. application.ex
content is:
which means that:
- After the application has started, the
:telemetry
application will invokehandle_event
after receiving a:hello
event. - As a consequence, the string
[Name telemetry: #{measurements.name}]
will be logged.
TestProject.hello("name")
has two effects:
- It sends a
:hello
message to the:telemetry
application. - 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.
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:
The Guix mix build system is insufficient since trying to build the application model package gives:
Even if the telemetry
package is in the store:
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:
guix pkg
↦successful build
guix pkg
↦runtime dependencies
guix pkg
↦inputs
guix pkg
↦.app
↦applications
inputs
,applications
↦runtime dependencies
runtime dependencies
,store
↦successful build
runtime dependencies
,store
↦store'
guix pkg
↦bin
runtime dependencies
,ERL_LIBS
,bin
↦bin'
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?
- An environment is built:
$ guix shell -C -D -f test-project-package.scm --preserve=LC_ALL - The telemetry package exists:
[env]$ echo /gnu/store/* | tr ' ' '\n' | grep telemetry /gnu/store/k2blpmzb8z15idsppifq1dhkxhwpgh0k-erlang-telemetry-1.2.1 - 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 - The
test_project.app
Application Resource File liststelemetry
in itsapplications
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',[]}}]}. - 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 - 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!"
- 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] - 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") - Given a package, it is possible to deduce the runtime dependencies.
- A Guix package lists its dependencies in the
inputs
field. - Each item in
inputs
is apackage
- Each package has a name, for instance
erlang-name
orelixir-name
or someting else. - if
name
is also inapplications
, then: its is a runtime dependency. - All the runtime dependencies form the
runtime dependencies
.
- A Guix package lists its dependencies in the
- Given the runtime package dependencies, there are installed in the store.
(run-with-store (package->derivation runtime-dep))
should intall in the store the packageruntime-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