diff --git a/CHANGELOG.md b/CHANGELOG.md index 361ab5e..058253a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Bump upstream Vize crates 0.109 → 0.112. - Fix CSS AST print round-trip for `image-set(...)`. +- Automatically build NIF from source on targets without precompiled artifacts. ## 0.11.0 diff --git a/README.md b/README.md index 5614899..98e3576 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,33 @@ def deps do end ``` -Requires a Rust toolchain (`rustup` recommended). The NIF compiles automatically on `mix compile`. +Vize uses precompiled NIFs on supported targets. On targets without a +precompiled artifact, Vize automatically builds the NIF from source, if +required tools are available. + +Source builds require a Rust toolchain (`rustup` recommended) and Rustler +in your app's deps: + +```elixir +def deps do + [ + {:vize, "~> 0.11.0"}, + {:rustler, "~> 0.37", optional: true} + ] +end +``` + +To force a source build on any target: + +```sh +VIZE_EX_BUILD=1 mix deps.compile vize --force +``` + +You can also make source builds permanent with compile-time config: + +```elixir +config :rustler_precompiled, :force_build, vize: true +``` ## Usage diff --git a/lib/vize/native.ex b/lib/vize/native.ex index 4ce2aa2..e447507 100644 --- a/lib/vize/native.ex +++ b/lib/vize/native.ex @@ -1,18 +1,72 @@ +defmodule Vize.Native.Build do + @moduledoc false + + @targets ~w( + aarch64-apple-darwin + aarch64-unknown-linux-gnu + x86_64-apple-darwin + x86_64-unknown-linux-gnu + x86_64-unknown-linux-musl + ) + + def targets, do: @targets + + def force_build?(env_value, config_value, target \\ current_target()) do + env_value in ["1", "true"] or config_value == true or target not in @targets + end + + def ensure_rustler_available!(force_build?, rustler_loaded? \\ Code.ensure_loaded?(Rustler)) + def ensure_rustler_available!(false, _rustler_loaded?), do: :ok + + def ensure_rustler_available!(true, rustler_loaded?) do + unless rustler_loaded? do + raise """ + Vize needs to compile its Rust NIF from source, but Rustler is not available. + + Add Rustler to your application's dependencies: + + {:rustler, "~> 0.37", optional: true} + + Then run: + + mix deps.get + mix deps.compile vize --force + """ + end + + :ok + end + + defp current_target do + case RustlerPrecompiled.target() do + {:ok, "nif-" <> target} -> + target + |> String.split("-", parts: 2) + |> List.last() + + {:error, _reason} -> + nil + end + end +end + defmodule Vize.Native do version = Mix.Project.config()[:version] + force_build? = + Vize.Native.Build.force_build?( + System.get_env("VIZE_EX_BUILD"), + Application.compile_env(:rustler_precompiled, [:force_build, :vize], false) + ) + + Vize.Native.Build.ensure_rustler_available!(force_build?) + use RustlerPrecompiled, otp_app: :vize, crate: "vize_ex_nif", base_url: "https://github.com/elixir-volt/vize_ex/releases/download/v#{version}", - force_build: System.get_env("VIZE_EX_BUILD") in ["1", "true"], - targets: ~w( - aarch64-apple-darwin - aarch64-unknown-linux-gnu - x86_64-apple-darwin - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl - ), + force_build: force_build?, + targets: Vize.Native.Build.targets(), version: version @spec parse_sfc_nif(String.t()) :: {:ok, map()} | {:error, String.t()} diff --git a/test/vize_test.exs b/test/vize_test.exs index f1e219e..6498647 100644 --- a/test/vize_test.exs +++ b/test/vize_test.exs @@ -38,6 +38,26 @@ defmodule VizeTest do defp rewrite_css_url(%{"url" => from} = node, from, to), do: %{node | "url" => to} defp rewrite_css_url(node, _from, _to), do: node + describe "native build config" do + test "honors explicit source build settings" do + assert Vize.Native.Build.force_build?("1", false, "x86_64-unknown-linux-gnu") + assert Vize.Native.Build.force_build?("true", false, "x86_64-unknown-linux-gnu") + assert Vize.Native.Build.force_build?(nil, true, "x86_64-unknown-linux-gnu") + refute Vize.Native.Build.force_build?(nil, false, "x86_64-unknown-linux-gnu") + end + + test "source builds when no precompiled target is available" do + assert Vize.Native.Build.force_build?(nil, false, "x86_64-unknown-freebsd") + refute Vize.Native.Build.force_build?(nil, false, "x86_64-unknown-linux-gnu") + end + + test "raises a clear error when source build needs Rustler" do + assert_raise RuntimeError, ~r/Rustler is not available/, fn -> + Vize.Native.Build.ensure_rustler_available!(true, false) + end + end + end + describe "parse_sfc/1" do test "parses template block" do {:ok, descriptor} = Vize.parse_sfc(@simple_sfc)