defmodule Kinda.Prebuilt do require Logger alias Kinda.CodeGen.{NIFDecl, Wrapper} defmacro __using__(opts) do quote do require Logger opts = unquote(opts) otp_app = Keyword.fetch!(opts, :otp_app) opts = Keyword.put_new( opts, :force_build, Application.compile_env(:kinda, [:force_build, otp_app]) ) case RustlerPrecompiled.__using__(__MODULE__, opts) do {:force_build, _only_rustler_opts} -> contents = Kinda.Prebuilt.__using__(__MODULE__, opts) Module.eval_quoted(__MODULE__, contents) {:ok, config} -> @on_load :load_rustler_precompiled @rustler_precompiled_load_from config.load_from @rustler_precompiled_load_data config.load_data {otp_app, path} = @rustler_precompiled_load_from load_path = otp_app |> Application.app_dir(path) {meta, _binding} = Path.dirname(load_path) |> Path.join("kinda-meta-#{Path.basename(load_path)}.ex") |> File.read!() |> Code.eval_string() contents = Kinda.Prebuilt.__using__(__MODULE__, Keyword.put(opts, :meta, meta)) Module.eval_quoted(__MODULE__, contents) @doc false def load_rustler_precompiled 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} = @rustler_precompiled_load_from load_path = otp_app |> Application.app_dir(path) |> to_charlist() :erlang.load_nif(load_path, @rustler_precompiled_load_data) end {:error, precomp_error} when is_bitstring(precomp_error) -> precomp_error |> String.split("You can force the project to build from scratch with") |> List.first() |> String.trim() |> Kernel.<>(""" You can force the project to build from scratch with: config :kinda, :force_build, #{otp_app}: true """) |> raise {:error, precomp_error} -> raise precomp_error end end end defp nif_ast(kinds, nifs, root_module, forward_module) do # generate stubs for generated NIFs Logger.debug("[Kinda] generating NIF wrappers, forward_module: #{inspect(forward_module)}") extra_kind_nifs = kinds |> Enum.map(&NIFDecl.from_resource_kind/1) |> List.flatten() for nif <- nifs ++ extra_kind_nifs do nif = case nif do {wrapper_name, arity} when is_atom(wrapper_name) and is_integer(arity) -> %NIFDecl{ wrapper_name: wrapper_name, nif_name: Module.concat(root_module, wrapper_name), arity: arity } _ -> nif end args_ast = Macro.generate_unique_arguments(nif.arity, __MODULE__) %NIFDecl{wrapper_name: wrapper_name, nif_name: nif_name} = nif wrapper_name = if is_bitstring(wrapper_name) do String.to_atom(wrapper_name) else wrapper_name end quote do @doc false def unquote(nif_name)(unquote_splicing(args_ast)), do: raise( "NIF for resource kind is not implemented, or failed to load NIF library. Function: :\"#{unquote(nif_name)}\"/#{unquote(nif.arity)}" ) def unquote(wrapper_name)(unquote_splicing(args_ast)) do refs = Kinda.unwrap_ref([unquote_splicing(args_ast)]) ret = apply(__MODULE__, unquote(nif_name), refs) unquote(forward_module).check!(ret) end end end |> List.flatten() end # generate resource modules defp load_ast(dest_dir, lib_name) do quote do # setup NIF loading @on_load :kinda_on_load @dest_dir unquote(dest_dir) def kinda_on_load do require Logger nif_path = Path.join(@dest_dir, "lib/#{unquote(lib_name)}") dylib = "#{nif_path}.dylib" so = "#{nif_path}.so" if File.exists?(dylib) do File.ln_s(dylib, so) end Logger.debug("[Kinda] loading NIF, path: #{nif_path}") with :ok <- :erlang.load_nif(nif_path, 0) do Logger.debug("[Kinda] NIF loaded, path: #{nif_path}") :ok else {:error, {:load_failed, msg}} when is_list(msg) -> Logger.error("[Kinda] NIF failed to load, path: #{nif_path}") Logger.error("[Kinda] #{msg}") :abort error -> Logger.error( "[Kinda] NIF failed to load, path: #{nif_path}, error: #{inspect(error)}" ) :abort end end end end # A helper function to extract the logic from __using__ macro. @doc false def __using__(root_module, opts) do code_gen_module = Keyword.fetch!(opts, :code_gen_module) kinds = code_gen_module.kinds() forward_module = Keyword.fetch!(opts, :forward_module) if opts[:force_build] do nifs = opts[:nifs] || [] %{dest_dir: dest_dir, lib_name: lib_name} = Wrapper.gen_and_build_zig(opts) nif_ast(kinds, nifs, root_module, forward_module) ++ [load_ast(dest_dir, lib_name)] else nifs = opts |> Keyword.fetch!(:meta) nif_ast(kinds, nifs, root_module, forward_module) end end end