Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--open doesn't work with Elixir modules, only erlang modules #11

Open
silbermm opened this issue Jan 22, 2022 · 1 comment
Open

--open doesn't work with Elixir modules, only erlang modules #11

silbermm opened this issue Jan 22, 2022 · 1 comment
Labels
bug Something isn't working help wanted Extra attention is needed
Milestone

Comments

@silbermm
Copy link
Owner

silbermm commented Jan 22, 2022

I think this has something to do with the escript build, because it works fine inside of iex.

I've tried a few different solutions:

  • added embed_elixir: true to the escript config
  • added elixir to the list of applications
  • set strip_beams: false

In the end, none of those worked.

The error I get is:

** (ArgumentError) unknown application: :elixir
    (elixir 1.13.1) lib/application.ex:907: Application.app_dir/1
    (elixir 1.13.1) lib/application.ex:934: Application.app_dir/2
    (iex 1.13.1) lib/iex/introspection.ex:170: IEx.Introspection.open_mfa/3
    (iex 1.13.1) lib/iex/introspection.ex:88: IEx.Introspection.open/1
    (elixir 1.13.1) lib/kernel/cli.ex:126: anonymous fn/3 in Kernel.CLI.exec_fun/2
@silbermm silbermm added bug Something isn't working help wanted Extra attention is needed labels Jan 22, 2022
@silbermm silbermm added this to the 0.1.4 milestone Jan 22, 2022
@ghost
Copy link

ghost commented Apr 6, 2022

The question is:
Why does it work with Erlang module?

Digging into the source code of IEx.Helpers.open/1,
We find that the problematic function is:

@elixir_apps ~w(eex elixir ex_unit iex logger mix)a
  @otp_apps ~w(kernel stdlib)a
  @apps @elixir_apps ++ @otp_apps

  defp rewrite_source(module, source) do
    case :application.get_application(module) do
      {:ok, app} when app in @apps ->
        Application.app_dir(app, rewrite_source(source))

      _ ->
        beam_path = :code.which(module)

        if is_list(beam_path) and List.starts_with?(beam_path, :code.root_dir()) do
          app_vsn = beam_path |> Path.dirname() |> Path.dirname() |> Path.basename()
          Path.join([:code.root_dir(), "lib", app_vsn, rewrite_source(source)])
        else
          List.to_string(source)
        end
    end
  end

Application.app_dir is failing,
When the alternate path with :code.which works.

Well if :code.which works, what does it return for both of our cases?

I modified the source code of help_command.ex:

def process(%{topic: <<":" <> erlang_module>>, open: open}) do
  IO.puts(:code.which(:"#{erlang_module}"))

And:

def process(%{topic: topic, open: open}) do
  IO.puts(:code.which(:"Elixir.#{topic}"))

Recompiling and reinstalling the escript,
I ran:

$ exdoc Enum --open
/usr/home/rowland/.asdf/install/elixir/1.13.3-otp-24/.mix/escripts/exdoc/Elixir.Enum.beam
<error message>
$ exdoc :lists --open
/home/rowland/.asdf/installs/erlang/24.3.2/lib/stdlib-3.17.1/ebin/lists.beam

Note that in the first case, Elixir is bundled into the escript itself,
But in the second case, it's using my local installation of Erlang.
Since rewrite_source/2 uses the directory it finds,
Even if we switched Elixir to use the :code.which result,
It wouldn't find the .ex files we need.

This makes sense when we remember that escripts produce an executable that can be run on any system with Erlang installed.
Escript pre-supposes that a system has Erlang on it, and doesn't bundle the code inside the escript.
Opting instead to use the local installation, wherever it is.

A potential solution:
Since we're using mix to install the escript,
We can presuppose that the machine has Elixir installed,
After all it wouldn't be able to compile the script otherwise.

Looking again at rewrite_source:

@elixir_apps ~w(eex elixir ex_unit iex logger mix)a
  @otp_apps ~w(kernel stdlib)a
  @apps @elixir_apps ++ @otp_apps

 defp rewrite_source(module, source) do
    case :application.get_application(module) do
      {:ok, app} when app in @apps ->
        Application.app_dir(app, rewrite_source(source))

There are only 8 different apps that we need directories for,
We could compute those 8 paths at compilation,
Or have the user set an environment variable, such as ELIXIR_SOURCE_PATH
And write a custom open function to use those paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

1 participant