jfcalvo
jfcalvo

Reputation: 485

Checking Erlang OTP Release to execute different functions on Elixir

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:

  1. Is this a correct way of execute code based in the OTP release version?
  2. Any better obvious way to address this problem?

Thanks.

Upvotes: 1

Views: 366

Answers (2)

Hauleth
Hauleth

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

Aleksei Matiushkin
Aleksei Matiushkin

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

Related Questions