Support a configurable module lookup from a path#20
Conversation
Erlang has a constraint that ensures there is a mapping from filename to module name: ``` foo.erl => -module(foo) bar.erl => -module(bar) ``` This makes looking up a module based on a path name deterministic. This is not the case in other BEAM languages such as Elixir. In Elixir, there is no mapping between the file name and the module name. It is also possible for an Elixir file to contain multiple modules. In order to allow edb to be used in Elixir, we need a way to configure the mapping from path to module. However, instead of baking in support just for Elixir, it would be ideal to have this be generic. This commit introduces a configurable function which can be provided on the target and called with `edb:eval`. This is configured using the `file_to_module_cb` application config. This is expected to return a list of modules, which will then be used for setting breakpoints on the module. This does introduce another issue though. When removing a breakpoint, if the module is changed in any way, or it is not possible to call the same lookup function, it may not be possible to unset the breakpoint. For this reason, the lookup is cached, and if the breakpoints are set to any empty list, then the cached module lookup is used and removed from the cache.
|
Hi @Gazler! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
Thanks for looking into this @Gazler! Configuring language specific overrides via an application env makes sense, but by passing simply a function callback, it seems to me like we fail to keep it general enough. For example, to make things work for Elixir we'd currently need to add Elixir-specific logic to edb, like extending the DAP state with the source cache or assuming that the callback needs to be evaluated on the debuggee. So I'd propose we instead pass in configuration a module implementing a new behaviour, say We'd need to add here a default implementation for Erlang, which will serve as reference for other languages; and the Elixir implementation would then have the What do you think? |
Maybe I'm misunderstanding here, I'm not sure this is necessarily Elixir specific, but this is definitely the requirement today since the function needs to be called in the context of Elixir (or Mix) to list the sources. Perhaps you mean Elixir specific in the sense that neither of these things are required for the Erlang implementation, in which case I totally agree.
I like this idea. It defines a concrete API for the expectation of the module. For example, in the code I proposed, there is an implicit expectation that the provided callback function maps from a What I'd like to know, is how do we provide this module to edb? What I did for this implementation, is have a script that is called via the edb integration in the IDE (in this case a neovim plugin, but the same logic applies for say, VSCode) Where we provide a script that does something like: defmodule Edb.MixSourceResolver do
def install do
Application.put_env(:edb, :file_to_module_cb, {:eval, &modules_for_source/1})
end
def modules_for_source(path), do: ...
end
Edb.MixSourceResolver.install()Do you envision something similar, but instead of I think if we do it that way, we need to define it before reverse attach is called. However, this would be simpler as we can do something like: I think this will depend on where the module lives though: Elixir stdlib module is available for "-eval": User application level module is not available for "-eval": |
|
Probably worth noting that the other thing added here is merging lines for when there are multiple modules with breakpoints. This likely wouldn't be covered if we just had a |
Yes, I meant this in that sense,
Good question. Thinking out loud a bit more, an alternative to an app env would be to extend the CLI with an optional arg, say, That way one could then re-package edb for other languages by providing the beams for edb + behaviour, plus with a wrapper script, say,
Btw, I'm not sure I understood this part. These However this makes me think that if, say, the Elixir behaviour implemention needs to run code on the debuggee (e.g. via |
This seems like it could be feasible.
Right, I was talking about the debuggee here. For example, for the erlang config we have something like: We'd need variant for Elixir. For testing this, I was using an elixir script that executes the reverse_attach code after setting the Again, the depends largely on eval being use the debuggee node for the config. Which is probably not the intention for a generic implementation, especially since there's no need to eval with erlang. We could potentially add an
Right, the elixir version is going likely to depend on https://mix.hexdocs.pm/Mix.Task.Compiler.html#manifests/1 which is a large reason why eval is required. |
|
I have very little detail on this but I think some sort of callback would be best because we can evolve and improve it directly from Elixir. Erlang logic is simple but could be moved to the default callback to stress-test the logic. |
Erlang has a constraint that ensures there is a mapping from filename to module name:
This makes looking up a module based on a path name deterministic. This is not the case in other BEAM languages such as Elixir. In Elixir, there is no mapping between the file name and the module name. It is also possible for an Elixir file to contain multiple modules.
In order to allow edb to be used in Elixir, we need a way to configure the mapping from path to module. However, instead of baking in support just for Elixir, it would be ideal to have this be generic.
This commit introduces a configurable function which can be provided on the target and called with
edb:eval. This is configured using thefile_to_module_cbapplication config. This is expected to return a list of modules, which will then be used for setting breakpoints on the module.This does introduce another issue though. When removing a breakpoint, if the module is changed in any way, or it is not possible to call the same lookup function, it may not be possible to unset the breakpoint. For this reason, the lookup is cached, and if the breakpoints are set to any empty list, then the cached module lookup is used and removed from the cache.