defmodule Mix.Tasks.Compile.Clixir do @moduledoc """ This tasks collects all the fragments that the Clixir macro invocations have generated, and does the finishing job. You need to add this compiler to the Mix configuration of your project, typically as a step between the regular compilation and elixir_make: def project() do app: myapp, compilers: Mix.compilers ++ [:clixir, :elixir_make] end The source is generated in c_src/<>.c and will be overwritten! """ require Logger def run(_args) do clixir_dir = Path.join(Mix.Project.build_path(), "clixir") if File.exists?(clixir_dir) do generate_code(clixir_dir) else Logger.warn("No Clixir files found in #{clixir_dir}, skipping final code generation step") :noop end #generate_c_code #generate_gperf_code #invoke_gperf end defp generate_code(clixir_dir) do File.mkdir("c_src") app_name = Mix.Project.config |> Keyword.get(:app) |> Atom.to_string() output_file_name = Path.join("c_src", app_name <> ".c") output_file = File.open!(output_file_name, [:write]) IO.puts(output_file, "#include ") for c_code_file <- Path.wildcard(clixir_dir <> "/*.c") do code = File.read!(c_code_file) IO.write(output_file, code) IO.write(output_file, "\n") end build_gperf_fun(clixir_dir, output_file) build_dispatch_fun(output_file) File.close(output_file) :ok end defp build_gperf_fun(clixir_dir, output_file) do gperf_filename = tmpfile() <> ".gperf" gperf_file = File.open!(gperf_filename, [:write]) IO.write gperf_file, """ struct dispatch_entry { char *name; void (*dispatch_func)(const char *buf, unsigned short len, int *index); }; %% """ for gperf_part <- Path.wildcard(clixir_dir <> "/*.gperf") do code = File.read!(gperf_part) IO.write(gperf_file, code) end File.close(gperf_file) {result, 0} = System.cmd("gperf", ["-t", gperf_filename]) IO.puts(output_file, result) File.rm(gperf_filename) end defp build_dispatch_fun(output_file) do IO.puts output_file, """ void _dispatch_command(const char *buf, unsigned short len, int *index) { char atom[MAXATOMLEN]; struct dispatch_entry *dpe; assert(ei_decode_atom(buf, index, atom) == 0); dpe = in_word_set(atom, strlen(atom)); if (dpe != NULL) { (dpe->dispatch_func)(buf, len, index); } else { fprintf(stderr, "Dispatch function not found for [%s]\\\n", atom); } } """ end defp tmpfile, do: "/tmp/clixir-temp-#{node()}-#{:erlang.unique_integer}" end