Circuits_sim in a release

I have a project using circuits_i2c and I want to be able to run the project on my PC with circuits_sim. Following the documentation I wound up with this in my config.exs (qemu is the MIX_TARGET I want to use the simulated i2c bus for:

if config_target() == :qemu do
  config :circuits_i2c, default_backend: CircuitsSim.I2C.Backend
  config :circuits_sim,
  config: [
    {CircuitsSim.Device.MCP23008, bus_name: "i2c-0", address: 0x20},
  ]
end

This works for iex and I can see the MIX_TARGET take effect:

✦ ❯ iex -S mix
Generated circuits_sim_test app
Erlang/OTP 26 [erts-14.2.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Circuits.I2C.bus_names
[]
✦ ❯ MIX_TARGET=qemu iex -S mix
Erlang/OTP 26 [erts-14.2.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Circuits.I2C.bus_names
["i2c-0"]
iex(2)>

However, for releases built like this:

✦ ❯ MIX_TARGET=qemu mix release

Running the resulting release gives the following error, where circuits_i1c appears to try to load the NIF despite being apparently configured to use the circuits_sim backend:

✦ ❯ _build/qemu_dev/rel/circuits_sim_test/bin/circuits_sim_test start_iex
=CRASH REPORT==== 13-Aug-2025::18:14:17.622342 ===
  crasher:
    initial call: kernel:init/1
    pid: <0.675.0>
    registered_name: []
    exception exit: {on_load_function_failed,'Elixir.Circuits.I2C.Nif',
                        {error,
                            {load_failed,
                                "Failed to load NIF library: '/home/larry/projects/circuitsSimIssue/circuits_sim_test/_build/qemu_dev/rel/circuits_sim_test/lib/circuits_i2c-2.1.0/priv/i2c_nif.so: cannot open shared object file: No such file or directory'"}}}
      in function  init:run_on_load_handlers/0
      in call from kernel:init/1 (kernel.erl, line 227)
    ancestors: [kernel_sup,<0.668.0>]
    message_queue_len: 0
    messages: []
    links: [<0.670.0>]
    dictionary: []
    trap_exit: false
    status: running
    heap_size: 610
    stack_size: 28
    reductions: 234
  neighbours:

=SUPERVISOR REPORT==== 13-Aug-2025::18:14:17.622504 ===
    supervisor: {local,kernel_sup}
    errorContext: start_error
    reason: {on_load_function_failed,'Elixir.Circuits.I2C.Nif',
                {error,
                    {load_failed,
                        "Failed to load NIF library: '/home/larry/projects/circuitsSimIssue/circuits_sim_test/_build/qemu_dev/rel/circuits_sim_test/lib/circuits_i2c-2.1.0/priv/i2c_nif.so: cannot open shared object file: No such file or directory'"}}}
    offender: [{pid,undefined},
               {id,on_load},
               {mfargs,{proc_lib,start_link,[kernel,init,[on_load]]}},
               {restart_type,transient},
               {significant,false},
               {shutdown,2000},
               {child_type,worker}]

=CRASH REPORT==== 13-Aug-2025::18:14:17.623120 ===
  crasher:
    initial call: application_master:init/4
    pid: <0.667.0>
    registered_name: []
    exception exit: {{shutdown,
                      {failed_to_start_child,on_load,
                       {on_load_function_failed,'Elixir.Circuits.I2C.Nif',
                        {error,
                         {load_failed,
                          "Failed to load NIF library: '/home/larry/projects/circuitsSimIssue/circuits_sim_test/_build/qemu_dev/rel/circuits_sim_test/lib/circuits_i2c-2.1.0/priv/i2c_nif.so: cannot open shared object file: No such file or directory'"}}}}},
                     {kernel,start,[normal,[]]}}
      in function  application_master:init/4 (application_master.erl, line 142)
    ancestors: [<0.666.0>]
    message_queue_len: 1
    messages: [{'EXIT',<0.668.0>,normal}]
    links: [<0.666.0>,<0.665.0>]
    dictionary: []
    trap_exit: true
    status: running
    heap_size: 987
    stack_size: 28
    reductions: 208
  neighbours:

=INFO REPORT==== 13-Aug-2025::18:14:17.623386 ===
    application: kernel
    exited: {{shutdown,
                 {failed_to_start_child,on_load,
                     {on_load_function_failed,'Elixir.Circuits.I2C.Nif',
                         {error,
                             {load_failed,
                                 "Failed to load NIF library: '/home/larry/projects/circuitsSimIssue/circuits_sim_test/_build/qemu_dev/rel/circuits_sim_test/lib/circuits_i2c-2.1.0/priv/i2c_nif.so: cannot open shared object file: No such file or directory'"}}}}},
             {kernel,start,[normal,[]]}}
    type: permanent

Kernel pid terminated (application_controller) ("{application_start_failure,kernel,{{shutdown,{failed_to_start_child,on_load,{on_load_function_failed,'Elixir.Circuits.I2C.Nif',{error,{load_failed,\"Failed to load NIF library: '/home/larry/projects/circuitsSimIssue/circuits_sim_test/_build/qemu_dev/rel/circuits_sim_test/lib/circuits_i2c-2.1.0/priv/i2c_nif.so: cannot open shared object file: No such file or directory'\"}}}}},{kernel,start,[normal,[]]}}}")

Crash dump is being written to: erl_crash.dump...done

I want to be able to build a release using circuits_sim, because I am testing more than just this elixir app in qemu and my higher level nix build that produces that VM needs a release as input. Is there a way to make that work?

Hmm. I think that I only have guesses since I’m not sure what you have configured for MIX_TARGET=qemu. Would it be possible for you to create a reproduction case for this and post an issue to the circuits_sim GitHub? I’m quite sure there could at least be a better error message if there is, indeed, an error.

Hi Frank, yes of course I’d really appreciate you taking a look at it.
Here’s a pretty minimal reproduction case. I use Nix, but I think if you ignore that and just go to the mix project in there it should work the same: