defmodule Mix.Tasks.Firebird.Init do @moduledoc """ Initializes Firebird WASM integration in your project. This task sets up everything needed to work with WASM modules: - Creates a `wasm/` directory for your WASM files - Adds sample configuration - Creates example usage code ## Usage mix firebird.init mix firebird.init --example math mix firebird.init --rust mix firebird.init --phoenix mix firebird.init --rust --phoenix --example math ## Options - `--example` - Generate example WASM integration (math, string) - `--rust` - Generate a Rust WASM scaffold in `native/` - `--phoenix` - Generate Phoenix helper modules - `--force` - Overwrite existing files """ use Mix.Task @shortdoc "Initialize Firebird WASM integration in your project" @impl Mix.Task def run(args) do {opts, _, _} = OptionParser.parse(args, switches: [example: :string, force: :boolean, rust: :boolean, phoenix: :boolean], aliases: [e: :example, f: :force, r: :rust, p: :phoenix] ) Mix.shell().info("🔥 Initializing Firebird WASM integration...") create_directories() copy_sample_wasm() create_config() create_wasm_module_template() if opts[:example] do create_example(opts[:example]) end if opts[:rust] do create_rust_scaffold() end if opts[:phoenix] do create_phoenix_helpers() end create_test_template() print_success_message() end defp create_directories do Mix.Generator.create_directory("wasm") Mix.Generator.create_directory("lib/wasm_modules") Mix.shell().info(" Created wasm/ directory") Mix.shell().info(" Created lib/wasm_modules/ directory") end defp copy_sample_wasm do sample_src = Firebird.sample_wasm_path() sample_dest = "wasm/sample_math.wasm" if File.exists?(sample_src) && !File.exists?(sample_dest) do File.cp!(sample_src, sample_dest) Mix.shell().info(" Copied sample_math.wasm to wasm/ (try it now!)") end end defp create_config do config_path = "config/firebird.exs" if File.exists?(config_path) do Mix.shell().info(" #{config_path} already exists, skipping") else config_content = """ import Config # Firebird WASM Configuration config :firebird, # Directory containing WASM files wasm_path: "wasm", # Default memory limit for WASM instances (64MB) memory_limit: 67_108_864, # Default execution timeout (5 seconds) timeout: 5_000, # Enable WASI support wasi: true """ File.mkdir_p!("config") File.write!(config_path, config_content) Mix.shell().info(" Created #{config_path}") end end defp create_wasm_module_template do template_path = "lib/wasm_modules/example.ex" if File.exists?(template_path) do Mix.shell().info(" #{template_path} already exists, skipping") else template_content = ~S''' defmodule WasmModules.Example do @moduledoc """ Example WASM module wrapper. Replace with your actual WASM module path and functions. Add to your supervision tree: `children = [WasmModules.Example]` Then call: `WasmModules.Example.add(5, 3)` """ use Firebird, wasm: "wasm/example.wasm" wasm_fn :add, args: 2 wasm_fn :multiply, args: 2 end ''' File.write!(template_path, template_content) Mix.shell().info(" Created #{template_path}") end end defp create_example("math") do example_path = "lib/wasm_modules/math.ex" example_content = ~S''' defmodule WasmModules.Math do @moduledoc """ Math operations implemented in WASM. Add to your supervision tree: children = [WasmModules.Math] Then call functions: {:ok, [8]} = WasmModules.Math.add(5, 3) [55] = WasmModules.Math.fibonacci!(10) """ use Firebird, wasm: "wasm/math.wasm" wasm_fn :add, args: 2, doc: "Add two integers" wasm_fn :multiply, args: 2, doc: "Multiply two integers" wasm_fn :fibonacci, args: 1, doc: "Calculate fibonacci number" end ''' File.write!(example_path, example_content) Mix.shell().info(" Created #{example_path} (math example)") end defp create_example(_other) do Mix.shell().info(" Unknown example type, skipping") end defp create_rust_scaffold do File.mkdir_p!("native/src") # Cargo.toml cargo_toml = """ [package] name = "wasm_native" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [profile.release] opt-level = "z" lto = true strip = true """ File.write!("native/Cargo.toml", cargo_toml) # src/lib.rs lib_rs = ~S""" #[no_mangle] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b } #[no_mangle] pub extern "C" fn multiply(a: i32, b: i32) -> i32 { a * b } #[no_mangle] pub extern "C" fn fibonacci(n: i32) -> i32 { if n <= 1 { return n; } let mut a = 0i32; let mut b = 1i32; for _ in 2..=n { let tmp = b; b = a + b; a = tmp; } b } """ File.write!("native/src/lib.rs", lib_rs) # build.sh build_sh = """ #!/bin/bash # Build Rust WASM module set -e cd "$(dirname "$0")" cargo build --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/wasm_native.wasm ../wasm/ echo "Built wasm/wasm_native.wasm" """ File.write!("native/build.sh", build_sh) File.chmod!("native/build.sh", 0o755) Mix.shell().info(" Created native/ Rust scaffold") end defp create_phoenix_helpers do helper_path = "lib/wasm_modules/phoenix_helpers.ex" helper_content = ~S''' defmodule WasmModules.PhoenixHelpers do @moduledoc """ Phoenix integration helpers for Firebird WASM modules. ## Setup Add a WASM pool to your application supervision tree: # application.ex children = [ {Firebird.Pool, wasm: "wasm/math.wasm", size: 4, name: :wasm_math}, MyAppWeb.Endpoint ] ## Controller Usage defmodule MyAppWeb.ComputeController do use MyAppWeb, :controller def compute(conn, %{"a" => a, "b" => b}) do [result] = Firebird.Pool.call!(:wasm_math, :add, [ String.to_integer(a), String.to_integer(b) ]) json(conn, %{result: result}) end end ## LiveView Usage defmodule MyAppWeb.ComputeLive do use MyAppWeb, :live_view def mount(_params, _session, socket) do {:ok, assign(socket, :result, nil)} end def handle_event("compute", %{"n" => n}, socket) do [result] = Firebird.Pool.call!(:wasm_math, :fibonacci, [ String.to_integer(n) ]) {:noreply, assign(socket, :result, result)} end end ## One-shot Usage (no pool needed) # For infrequent calls, use Firebird.run!/3 directly: [result] = Firebird.run!("wasm/math.wasm", :add, [5, 3]) """ end ''' File.write!(helper_path, helper_content) Mix.shell().info(" Created #{helper_path}") end defp create_test_template do test_path = "test/wasm_test.exs" if File.exists?(test_path) do Mix.shell().info(" #{test_path} already exists, skipping") else File.mkdir_p!("test") content = ~S''' defmodule WasmTest do use ExUnit.Case import Firebird.TestHelpers # Uses the bundled sample math.wasm setup_wasm "wasm/sample_math.wasm" test "add two numbers", %{wasm: wasm} do assert_wasm_call wasm, :add, [5, 3], [8] end test "multiply", %{wasm: wasm} do assert_wasm_call wasm, :multiply, [4, 7], [28] end test "fibonacci", %{wasm: wasm} do assert_wasm_call wasm, :fibonacci, [10], [55] end test "exports are available", %{wasm: wasm} do assert_wasm_exports wasm, [:add, :multiply, :fibonacci] end test "type signatures", %{wasm: wasm} do assert_wasm_type wasm, :add, {[:i32, :i32], [:i32]} end end ''' File.write!(test_path, content) Mix.shell().info(" Created #{test_path}") end end defp print_success_message do Mix.shell().info(""" 🔥 Firebird WASM integration initialized! Try it NOW (zero setup): iex -S mix iex> Firebird.check!() # Verify setup works iex> Firebird.demo() # See it in action # Or call the sample WASM directly: iex> {:ok, [8]} = Firebird.run("wasm/sample_math.wasm", :add, [5, 3]) Next steps: 1. Add your own .wasm files to wasm/ 2. Generate a typed wrapper from any .wasm file: mix firebird.gen wasm/your_module.wasm --module MyApp.YourModule 3. Run the generated tests: mix test test/wasm_test.exs See CHEATSHEET.md for a quick API reference. Happy WASM coding! 🚀 """) end end