Current section
Files
Jump to
Current section
Files
lib/mix/tasks/firebird.init.ex
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 = "1.0.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