defmodule Rustler do @moduledoc """ Provides compile-time configuration for a NIF module. When used, Rustler expects the `:otp_app` as option. The `:otp_app` should point to the OTP application that the dynamic library can be loaded from. For example: defmodule MyNIF do use Rustler, otp_app: :my_nif end This allows the module to be configured like so: config :my_nif, MyNIF, crate: :my_nif, load_data: [1, 2, 3] ## Configuration options * `:cargo` - Specify how to envoke the rust compiler. Options are: - `:system` (default) - use `cargo` from the system (must be in `$PATH`) - `{:system, }` - use `cargo` from the system with the given channel. Specified as a string, passed directly to `cargo` (e.g. "+nightly"). - `{:rustup, }` - use `rustup` to specify which channel to use. Available options include: `:stable`, `:beta`, `:nightly`, or a string which specifies a specific version (e.g. `"1.39.0"`). - `{:bin, "/path/to/binary"}` - provide a specific path to `cargo`. * `:crate` - the name of the Rust crate, if different from your `otp_app` value. If you have more than one crate in your project, you will need to be explicit about which crate you intend to use. * `:default_features` - a boolean to specify whether the crate's default features should be used. * `:env` - Specify a list of environment variables when envoking the compiler. * `:features` - a list of features to enable when compiling the crate. * `:load_data` - Any valid term. This value is passed into the NIF when it is loaded (default: `0`) * `:load_data_fun` - `{Module, :function}` to dynamically generate `load_data`. Default value: `nil`. This parameter is mutually exclusive with `load_data` which means that `load_data` has to be set to it's default value. Example defmodule NIF do use Rustler, load_data_fun: {Deployment, :nif_data} end defmodule Deployment do def nif_data do :code.priv_dir(:otp_app) |> IO.iodata_to_binary() end end * `:load_from` - This option allows control over where the final artifact should be loaded from at runtime. By default the compiled artifact is loaded from the owning `:otp_app`'s `priv/native` directory. This option comes in handy in combination with the `:skip_compilation?` option in order to load pre-compiled artifacts. To override the default behaviour specify a tuple: `{:my_app, "priv/native/"}`. Due to the way `:erlang.load_nif/2` works, the artifact should not include the file extension (i.e. `.so`, `.dll`). * `:mode` - Specify which mode to compile the crate with (default: `:release`) * `:path` - By default, rustler expects the crate to be found in `native/` in the root of the project. Use this option to override this. * `:skip_compilation?` - This option skips envoking the rust compiler. Specify this option in combination with `:load_from` to load a pre-compiled artifact. * `:target` - Specify a compile [target] triple. Any of the above options can be passed directly into the `use` macro like so: defmodule MyNIF do use Rustler, otp_app: :my_nif, crate: :some_other_crate, load_data: :something end [target]: https://doc.rust-lang.org/stable/rustc/platform-support.html """ defmacro __using__(opts) do quote bind_quoted: [opts: opts] do otp_app = Keyword.fetch!(opts, :otp_app) env = Application.compile_env(otp_app, __MODULE__, []) config = Rustler.Compiler.compile_crate(otp_app, env, opts) for resource <- config.external_resources do @external_resource resource end if config.lib do @load_from config.load_from @load_data config.load_data @load_data_fun config.load_data_fun @before_compile Rustler end end end defmacro __before_compile__(env) do default_load_data_value = %Rustler.Compiler.Config{}.load_data default_fun_value = %Rustler.Compiler.Config{}.load_data_fun load_data = Module.get_attribute(env.module, :load_data) load_data_fun = Module.get_attribute(env.module, :load_data_fun) load_data = case {load_data, load_data_fun} do {load_data, ^default_fun_value} -> quote do unquote(load_data) end {^default_load_data_value, {module, function}} when is_atom(module) and is_atom(function) -> quote do apply(unquote(module), unquote(function), []) end {^default_load_data_value, provided_value} -> raise """ `load_data_fun` has to be `{Module, :function}`. Instead received: #{inspect(provided_value)} """ {load_data, load_data_fun} -> raise """ Only `load_data` or `load_data_fun` can be provided. Instead received: >>> load_data: #{inspect(load_data)} >>> load_data_fun: #{inspect(load_data_fun)} """ end quote do @on_load :rustler_init @doc false def rustler_init do # Remove any old modules that may be loaded so we don't get # {:error, {:upgrade, 'Upgrade not supported by this NIF library.'}} :code.purge(__MODULE__) {otp_app, path} = @load_from load_path = otp_app |> Application.app_dir(path) |> to_charlist() load_data = unquote(load_data) :erlang.load_nif(load_path, load_data) end end end end