PatrickSachs
Hello there,
I have the following scenario:
I have a library my_lib that is used in my_app. my_lib has a configuration file option that accepts a list of modules. Based on the list of modules provided, a Plug is generated.
The config in my_app is defined like this:
config :my_lib,
modules: [MyApp.Plugs.Plug1, MyApp.Plugs.Plug2]
The code in my_lib looks like this:
modules = Application.get_env(:my_lib, :modules)
for module <- modules do
quote do
forward "/module/#{unquote module}", to: unquote(module)
end
end
This compiles, but a request to /module/Elixir.MyApp.Plugs.Plug1 just yields a 404 error. Interestingly, I also get a warning in VS Code in the for line, that modules cannot be iterated since it is nil, which to my understanding cannot be the case since I defined a default value of [] for it the mix.exs file of my_lib (I can also certainly confirm that it is not nil, since logging it prints the expected value).
def application do
[
mod: {MyLib.Application, []},
env: [modules: []]
]
end
At this point I assumed that I either made an error in my meta-programming or since my_lib is compiled before my_app, that its modules are simply not yet available.
According to the documentation Code.ensure_compiled/1 would be a good candidate to wait for the compilation of these modules. So I added a call to this function and printed out its results, which were quite baffling to me:
modules = Application.get_env(:my_lib, :modules)
for module <- modules do
compiled = Code.ensure_compiled(module)
IO.puts("ensure_compiled #{inspect module} -> #{inspect compiled}")
# = ensure_compiled MyApp.Plugs.Plug1 -> {:error, :nofile}
# = ensure_compiled MyApp.Plugs.Plug2 -> {:error, :nofile}
quote do
forward "/module/#{unquote module}", to: unquote(module)
end
end
Apparently these modules do not exist at all. However, calling this function directly in IEx, the call resolves successfully:
iex(1)> Code.ensure_loaded MyApp.Plugs.Plug1
{:module, MyApp.Plugs.Plug1}
If I had to take another guess the module resolution fails since my_lib is looking for a source file in its own lib folder and does not take other applications (my_app) into account.
- Is there any way to use modules of the “parent” app during compilation? If so, how would I go about that?
- What did I do wrong that Plug did not throw an error or return a 500, but just silently ignore my module (well, or just works for that matter
)? - Did I set the default config value incorrectly? It seems to work when executing, but since VS Code (using the elixir-ls extension) complains about it, I guess there must be something not quite right with it.
Thank you for your time! I realize that this is quite the wall of text ![]()
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
al2o3cr
I don’t think the code being compiled in
my_libcan see the code inmy_appat all - it would massively complicate dependency tracking if files in the application could cause libraries to recompile.Have you considered an approach like
Ecto.Repowhere callers sayuse MyLib, modules: [...etc...]in a module inmy_app? Theforloop and friends stay inmy_libbut the use of them happens whenmy_appis compiled.PatrickSachs
That’s a brilliant idea! Thank you so much for this. This also solves a future problem I’d have had when a user would like to create two “instances” of
my_lib. This way they can justuseit multiple times.One small problem, I’m sure I’m doing something wrong, but I’m running into the error, that
connfrom Plug cannot be found in my macro:I’ve tried wrapping
conninMacro.var, to no success. But to my understanding that shouldn’t even be necessary in this case.PatrickSachs
Not sure what the forum policy on bumping is - if it is allowed consider this topic bumped.
If not I’ll go ahead and open a new topic for this second question.
kip
Changing
conntovar!(conn)will compile cleanly I believe.PatrickSachs
This seems to have done the trick, although I am not sure why this is required in this case. Thank you very much!
Now I am running into a different macro issue entirely:
My
__using__macro takes anoptsargument which is a proplist with several options.Since I want to post-process the options at compile time before inserting them into code I’ve gone ahead and implemented it in the following way (the implementation of
normalize_moduleshould not be relevant):This creates an issue in the
unquote(modules)line, since the contents ofmodulesis no longer coming from the macro, but its own body:quotepart, which came with its own set of issues (including having to useCode.eval_quotedwhich I’d like to avoid).Huge thanks to everyone who has been helping me out so far!
kip
Remember that everything in the
quoteblock is actually runtime execution. Outside thequoteis compile time execution. Therefore you are probably after:Do note however that macros both receive and return AST. So
opts[:modules]may not be what you expect since it will be AST.You may find that you need the following signature for
MyLib.normalize_module/1:or alternatively:
PatrickSachs
I see, thank you. That’s quite unfortunate, since this feels like it makes this far more difficult than it has to be.
For example my input AST can look like the following:
Since the AST can be and will be deeply nested I’d be unable to use the “standard” elixir functions for interacting with data structures.
Also it feels wrong to make a function (the
normalize_modulefunction) dependent on receiving AST when it could potentially called at runtime with non-AST data.Calling
Macro.expandseems to do nothing with my AST.Is there a better way to do this?
I’d ideally not receive this option as AST but as a “normal” data structure, which contents I can after modifying turn into a literal and insert into the code.
kip
Since macros are primarily about manipulating code, not executing it, I’m not sure how your expectation could be met.
Nevertheless, you can evaluate AST at compile time with
Code.eval_quoted/3although as the documentation says:Using your example:
The benefit here isn’t really enough to warrant using
Code.eval_quoted/3so perhaps reverting closer to your original idea may be clearer since it too will get resolved at compile time (but in a later compiler expansion):PatrickSachs
I see, for some reason I always looked at them as a way of executing code at compile time, but this makes a lot more sense.
True, I never even considered the fact that a user might want to e.g. use a runtime configuration value. Then I guess I’ll budge and do the data transformation fully at runtime.
I initially wanted to avoid this to save performance at runtime, but this doesn’t seem to be a good way to do it. Maybe this is a case of premature optimization being evil again
Thanks for your time and patience. I wish I could mark the answers of multiple people as solution, but I’ll just go ahead and mark your last one.
Have a great weekend!
kip
In my somewhat lengthy reply you might have missed this version which is compile-time calculation of your modules: