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