Reputation: 485
I'm creating a new Elixir library that require to be executed with different versions of the language.
This library uses the Erlang :crypto.hmac
function but this function was replaced on version 22 of Erlang OTP by :crypto.mac
(with same functionality).
I'm using the following private macro to execute the newer or older functions:
defmacrop hmac(key, data) do
if System.otp_release() >= "22" do
quote do: :crypto.mac(:hmac, :sha256, unquote(key), unquote(data))
else
quote do: :crypto.hmac(:sha256, unquote(key), unquote(data))
end
end
And using it in the following way:
hmac(key, data)
Two questions:
Thanks.
Upvotes: 1
Views: 366
Reputation: 23586
Is this a correct way of execute code based in the OTP release version?
You should not check OTP version, but the application (in this case crypto
) version. As the OTP version can be different from the application version.
Any better obvious way to address this problem?
Yes, check if given function is exported instead of checking what is the version of the OTP/application.
There are 2 ways to do so, in runtime:
def hmac(key, data) do
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do
:crypto.mac(:mac, :sha256, key, data)
else
:crypto.hmac(:sha256, key, data)
end
end
Or during compile time:
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :mac, 4) do
def hmac(key, data), do: :crypto.mac(:mac, :sha256, key, data)
else
def hmac(key, data), do: :crypto.hmac(:sha256, key, data)
end
Upvotes: 3
Reputation: 121010
Macros are compile-time creatures. After compilation there is no trail of macro; instead, the AST it injected takes place in the release.
That said if you want it to be a compile-time, meaning you’ll need many environments to compile, and each release to be working on the same platform as it was compiled only, this is the correct approach.
If you want to assemble a release once and make it working on different target platforms, you’d better use Kernel.apply/3
as
def hmac(key, data) do
if String.to_integer(System.otp_release) >= 22,
do: apply(:crypto, :mac, [:hmac, :sha256, key, data]),
else: apply(:crypto, :hmac, [:sha256, key, data])
end
Sidenote: even if you want it to be compile-time, it’d be cleaner to declare different functions, rather than a macro.
if String.to_integer(System.otp_release) >= 22 do
def hmac(key, data),
do: :crypto.mac(:hmac, :sha256, key, data)
else
def hmac(key, data),
do: :crypto.hmac(:sha256, key, data)
end
This will be evaluated in the compile-time and the respective function will take place in the resulting BEAM code.
Upvotes: 1