Packages

Run WebAssembly from Elixir. Load WASM modules in Rust, Go, C — call them like native functions.

Current section

Files

Jump to
firebird lib mix tasks firebird.build.ex
Raw

lib/mix/tasks/firebird.build.ex

defmodule Mix.Tasks.Firebird.Build do
@moduledoc """
Build all WASM fixtures from source.
Compiles Rust, Go (TinyGo), and WAT source files into WASM modules.
## Usage
mix firebird.build # Build all fixtures
mix firebird.build --rust # Build only Rust fixtures
mix firebird.build --go # Build only Go fixtures
mix firebird.build --wat # Build only WAT fixtures
## Prerequisites
- **Rust**: `rustup target add wasm32-unknown-unknown`
- **Go**: TinyGo (`tinygo build`)
- **WAT**: `wat2wasm` from WABT toolkit
"""
use Mix.Task
@shortdoc "Build WASM fixtures from source (Rust, Go, WAT)"
@rust_targets [
{"fixtures/rust_math", "math", "fixtures/rust_math.wasm", "wasm32-unknown-unknown"},
{"fixtures/rust_algorithms", "algorithms", "fixtures/rust_algorithms.wasm",
"wasm32-unknown-unknown"},
{"fixtures/rust_float", "float_math", "fixtures/rust_float.wasm", "wasm32-unknown-unknown"},
{"fixtures/rust_wasi", "wasi_demo", "fixtures/rust_wasi.wasm", "wasm32-wasip1"}
]
@go_targets [
{"fixtures/go_math", "fixtures/go_math.wasm"},
{"fixtures/go_algorithms", "fixtures/go_algorithms.wasm"},
{"fixtures/go_bitwise", "fixtures/go_bitwise.wasm"}
]
@wat_targets [
{"fixtures/wat_examples/math.wat", "fixtures/math.wasm"},
{"fixtures/wat_examples/bitwise.wat", "fixtures/wat_bitwise.wasm"}
]
@impl Mix.Task
def run(args) do
build_all = args == [] or "--all" in args
build_rust = build_all or "--rust" in args
build_go = build_all or "--go" in args
build_wat = build_all or "--wat" in args
Mix.shell().info("🔥 Firebird WASM Builder\n")
results = []
results = if build_rust, do: results ++ build_rust_targets(), else: results
results = if build_go, do: results ++ build_go_targets(), else: results
results = if build_wat, do: results ++ build_wat_targets(), else: results
successes = Enum.count(results, fn {status, _} -> status == :ok end)
failures = Enum.count(results, fn {status, _} -> status == :error end)
skipped = Enum.count(results, fn {status, _} -> status == :skip end)
Mix.shell().info("\n📊 Results: #{successes} built, #{failures} failed, #{skipped} skipped")
end
defp build_rust_targets do
Mix.shell().info("🦀 Building Rust targets...")
if System.find_executable("cargo") do
Enum.map(@rust_targets, fn {dir, crate_name, output, target} ->
if File.dir?(dir) do
Mix.shell().info(" Building #{dir} (#{target})...")
case System.cmd("cargo", ["build", "--target", target, "--release"],
cd: dir,
stderr_to_stdout: true
) do
{_, 0} ->
src = Path.join([dir, "target/#{target}/release/#{crate_name}.wasm"])
File.cp!(src, output)
size = File.stat!(output).size
Mix.shell().info(" ✅ #{output} (#{size} bytes)")
{:ok, output}
{err, _} ->
Mix.shell().error(" ❌ Failed to build #{dir}: #{err}")
{:error, dir}
end
else
{:skip, dir}
end
end)
else
Mix.shell().info(" ⚠️ cargo not found, skipping Rust targets")
Enum.map(@rust_targets, fn {dir, _, _, _} -> {:skip, dir} end)
end
end
defp build_go_targets do
Mix.shell().info("🐹 Building Go targets...")
if System.find_executable("tinygo") do
Enum.map(@go_targets, fn {dir, output} ->
if File.dir?(dir) do
Mix.shell().info(" Building #{dir}...")
abs_output = Path.absname(output)
case System.cmd(
"tinygo",
["build", "-o", abs_output, "-target", "wasi", "-no-debug", "main.go"],
cd: dir,
stderr_to_stdout: true
) do
{_, 0} ->
size = File.stat!(output).size
Mix.shell().info(" ✅ #{output} (#{size} bytes)")
{:ok, output}
{err, _} ->
Mix.shell().error(" ❌ Failed to build #{dir}: #{err}")
{:error, dir}
end
else
{:skip, dir}
end
end)
else
Mix.shell().info(" ⚠️ tinygo not found, skipping Go targets")
Enum.map(@go_targets, fn {dir, _} -> {:skip, dir} end)
end
end
defp build_wat_targets do
Mix.shell().info("📝 Building WAT targets...")
if System.find_executable("wat2wasm") do
Enum.map(@wat_targets, fn {source, output} ->
if File.exists?(source) do
Mix.shell().info(" Building #{source}...")
case System.cmd("wat2wasm", [source, "-o", output], stderr_to_stdout: true) do
{_, 0} ->
size = File.stat!(output).size
Mix.shell().info(" ✅ #{output} (#{size} bytes)")
{:ok, output}
{err, _} ->
Mix.shell().error(" ❌ Failed: #{err}")
{:error, source}
end
else
{:skip, source}
end
end)
else
Mix.shell().info(" ⚠️ wat2wasm not found, skipping WAT targets")
Enum.map(@wat_targets, fn {source, _} -> {:skip, source} end)
end
end
end