Packages
rustler
0.19.0
0.38.0
0.37.3
0.37.1
0.37.0
retired
0.36.2
0.36.1
0.36.0
0.35.1
0.35.0
0.34.0
0.33.0
0.32.1
0.31.0
0.30.0
0.29.1
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.2
0.22.1
0.22.0
0.22.0-rc.2
0.22.0-rc.1
0.22.0-rc.0
0.21.1
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.1
0.17.0
0.16.0
0.10.1
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.1
0.1.0
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Mix compiler and runtime helpers for Rustler.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/compile.rustler.ex
defmodule Mix.Tasks.Compile.Rustler do
use Mix.Task
alias Rustler.Compiler.{Messages, Rustup}
@recursive true
def run(_args) do
config = Mix.Project.config()
crates = Keyword.get(config, :rustler_crates) || raise_missing_crates()
File.mkdir_p!(priv_dir())
Enum.map(crates, &compile_crate/1)
# Workaround for a mix problem. We should REALLY get this fixed properly.
_ = symlink_or_copy(config,
Path.expand("priv"),
Path.join(Mix.Project.app_path(config), "priv"))
end
defp priv_dir, do: "priv/native"
def compile_crate({id, config}) do
crate_path = Keyword.get(config, :path)
build_mode = Keyword.get(config, :mode, :release)
Mix.shell.info "Compiling NIF crate #{inspect id} (#{crate_path})..."
crate_full_path = Path.expand(crate_path, File.cwd!)
target_dir = Keyword.get(config, :target_dir,
Path.join([Mix.Project.build_path(), "rustler_crates", Atom.to_string(id)]))
cargo_data = check_crate_env(crate_full_path)
{output_name, output_type} =
case get_name(cargo_data, "lib") do
nil ->
case get_name(cargo_data, "bin") do
nil -> throw_error({:cargo_no_name, crate_path})
name -> {name, :bin}
end
name ->
{name, :lib}
end
compile_command =
make_base_command(Keyword.get(config, :cargo, :system))
|> make_no_default_features_flag(Keyword.get(config, :default_features, true))
|> make_features_flag(Keyword.get(config, :features, []))
|> make_build_mode_flag(build_mode)
|> make_platform_hacks(output_type, :os.type())
[cmd_bin | args] = compile_command
compile_return = System.cmd(cmd_bin, args, [
cd: crate_full_path,
stderr_to_stdout: true,
env: [{"CARGO_TARGET_DIR", target_dir} | Keyword.get(config, :env, [])],
into: IO.stream(:stdio, :line),
])
case compile_return do
{_, 0} -> nil
{_, code} -> raise "Rust NIF compile error (rustc exit code #{code})"
end
{src_file, dst_file} = make_file_names(output_name, output_type)
compiled_lib = Path.join([target_dir, Atom.to_string(build_mode), src_file])
destination_lib = Path.join(priv_dir(), dst_file)
# If the file exists already, we delete it first. This is to ensure that another
# process, which might have the library dynamically linked in, does not generate
# a segfault. By deleting it first, we ensure that the copy operation below does
# not write into the existing file.
File.rm(destination_lib)
File.cp!(compiled_lib, destination_lib)
end
defp make_base_command(:system), do: ["cargo", "rustc"]
defp make_base_command({:bin, path}), do: [path, "rustc"]
defp make_base_command({:rustup, version}) do
if Rustup.version == :none do
throw_error(:rustup_not_installed)
end
unless Rustup.version_installed?(version) do
throw_error({:rust_version_not_installed, version})
end
["rustup", "run", version, "cargo", "rustc"]
end
defp make_platform_hacks(args, :lib, {:unix, :darwin}) do
# Fix for https://github.com/hansihe/Rustler/issues/12
args ++ ["--", "--codegen", "link-args=-flat_namespace -undefined suppress"]
end
defp make_platform_hacks(args, _, _), do: args
defp make_no_default_features_flag(args, true), do: args ++ []
defp make_no_default_features_flag(args, false), do: args ++ ["--no-default-features"]
defp make_features_flag(args, []), do: args ++ []
defp make_features_flag(args, flags), do: args ++ ["--features", Enum.join(flags, ",")]
defp make_build_mode_flag(args, :release), do: args ++ ["--release"]
defp make_build_mode_flag(args, :debug), do: args ++ []
defp get_name(cargo_data, section),
do: Rustler.TomlParser.get_table_val(cargo_data, [section], "name")
def make_file_names(base_name, :lib) do
case :os.type() do
{:win32, _} -> {"#{base_name}.dll", "lib#{base_name}.dll"}
{:unix, :darwin} -> {"lib#{base_name}.dylib", "lib#{base_name}.so"}
{:unix, _} -> {"lib#{base_name}.so", "lib#{base_name}.so"}
end
end
def make_file_names(base_name, :bin) do
case :os.type() do
{:win32, _} -> {"#{base_name}.exe", "#{base_name}.exe"}
{:unix, _} -> {base_name, base_name}
end
end
def throw_error(error_descr) do
Mix.shell.error Messages.message(error_descr)
raise "Compilation error"
end
def check_crate_env(crate) do
unless File.dir?(crate) do
throw_error({:nonexistent_crate_directory, crate})
end
cargo_data =
case File.read("#{crate}/Cargo.toml") do
{:error, :enoent} ->
throw_error({:cargo_toml_not_found, crate})
{:ok, text} ->
Rustler.TomlParser.parse(text)
end
cargo_data
end
defp raise_missing_crates do
Mix.raise """
Missing required :rustler_crates option in mix.exs.
"""
end
# https://github.com/elixir-lang/elixir/blob/b13404e913fff70e080c08c2da3dbd5c41793b54/lib/mix/lib/mix/project.ex#L553-L562
defp symlink_or_copy(config, source, target) do
if config[:build_embedded] do
if File.exists?(source) do
File.rm_rf!(target)
File.cp_r!(source, target)
end
:ok
else
Mix.Utils.symlink_or_copy(source, target)
end
end
end