Can Elixir code detect whether it is executing at compile time? I would like to write something like the following macro:
defmacro foo() do
quote do
if is_compile_time? do
one_thing()
else
another_thing()
end
end
end
It’s presumably obvious how the code that the macro expands to can execute at runtime. It can execute at compile time in a scenario such as the following:
defmodule Bar do
def bar() do
foo() # invoke the macro
end
end
defmodule Amp do
@attr Bar.bar() # code that 'foo' macro expands to is executed at compile time
end
I have tried examining __ENV__, but unless I’m missing something, it offers no reliable diagnostic for compile time vs. runtime. I wonder if there is any other way. For anyone familiar with Common Lisp, I suppose I am looking for something that works a bit like eval-when.
By the way, I am asking largely out of curiosity. I’m aware that in general one should not need to perform such a check
The problem is that it is pretty hard to differentiate between “compile time” and “run time” as “compile time” is “run time with saving created modules to .beam files”.
You can check __CALLER__.function to detect whether macro was called within function body or outside of it.
compiling? = is_list(Process.get(:elixir_module_binaries)) But as @hauleth pointed out, difference between runtime and compile time is ephemeral. Compilation can happen in runtime, and anything can be called during compilation
Yes, sorry, I did not make this clear in my question. I’m interested to know if the code is being called during compilation, not necessarily at some mythical ‘compile time’.
Given that, I think your suggestion should work! Thank you.
That distinction doesn’t change anything he said though, you can still generate a module at runtime and compile it and for it what is happening will be “compile time”. You really should revise what you really want to achieve with this and consider other ways, this can very easily bite you in the rear in the most inopportune moment in the future.
Yes, I’m aware of that and clarified what I meant in the comment that you’re replying to: I’m interested to know if the code is being called during compilation. I assume hissssst’s expression would evaluate to true in the scenario you describe, which is exactly what I want.
To be precise, expression evaluates to true, only if the process is compiling the Elixir module. Some processes can be present during the compilation, but this expression would return false in them, since these processes are not compiling anything.
I use the following code in ex_cldr to detect if Elixir is compiling. The mechanism changed from Elixir 1.11, hence the conditional check:
def compiling? do
# TODO: When we depend on Elixir v1.11+ only, remove function_exported and elixir_compiler_pid
process_alive?(:can_await_module_compilation?) ||
process_alive?(:elixir_compiler_pid)
end
defp process_alive?(:can_await_module_compilation?) do
Code.ensure_loaded?(Code) &&
function_exported?(Code, :can_await_module_compilation?, 0) &&
apply(Code, :can_await_module_compilation?, [])
end
defp process_alive?(name) do
case Process.get(name) do
nil -> false
pid when is_pid(pid) -> true
end
end
BEAM does not create files on its own, Elixir and Erlang compiler do if run under some special conditions (e. g. from the command line.)
If you are compiling files manually, you are responsible for persisting them, if you wish. Here I do it, because Common Test needs tests to be available as beams.