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.new.ex
Raw

lib/mix/tasks/firebird.new.ex

defmodule Mix.Tasks.Firebird.New do
@moduledoc """
Creates a new Elixir project with Firebird WASM integration pre-configured.
Similar to `mix new` but with Firebird already set up.
## Usage
mix firebird.new my_app
mix firebird.new my_app --rust # Include Rust WASM scaffold
mix firebird.new my_app --sup # With supervision tree
## Options
- `--rust` - Include a Rust WASM scaffold in `native/`
- `--sup` - Generate with supervision tree (OTP app)
- `--module` - Module name (default: inferred from app name)
"""
use Mix.Task
@shortdoc "Create a new project with Firebird WASM integration"
@impl Mix.Task
def run(args) do
{opts, positional, _} =
OptionParser.parse(args,
switches: [rust: :boolean, sup: :boolean, module: :string],
aliases: [r: :rust, s: :sup, m: :module]
)
app_name =
case positional do
[name | _] -> name
[] -> Mix.raise("Usage: mix firebird.new APP_NAME [--rust] [--sup]")
end
module_name = opts[:module] || Macro.camelize(app_name)
app_path = app_name
if File.exists?(app_path) do
Mix.raise("Directory #{app_path} already exists!")
end
Mix.shell().info("🔥 Creating Firebird project #{app_name}...")
File.mkdir_p!(app_path)
File.mkdir_p!(Path.join(app_path, "lib/#{app_name}"))
File.mkdir_p!(Path.join(app_path, "test"))
File.mkdir_p!(Path.join(app_path, "wasm"))
write_mix_exs(app_path, app_name, module_name, opts)
write_lib(app_path, app_name, module_name, opts)
write_test(app_path, app_name, module_name)
write_config(app_path)
write_gitignore(app_path)
write_readme(app_path, app_name, module_name)
if opts[:rust] do
write_rust_scaffold(app_path)
end
Mix.shell().info("""
🔥 Your Firebird project is ready!
cd #{app_name}
mix deps.get
#{if opts[:rust], do: " # Build the Rust WASM module:\n cd native && ./build.sh && cd ..\n", else: ""}
Then try it in iex:
iex -S mix
iex> Firebird.check!() # Verify setup
iex> Firebird.demo() # See it in action
iex> wasm = Firebird.playground() # Get a pre-loaded instance
Or call WASM functions directly:
iex> {:ok, [8]} = Firebird.run(Firebird.sample_wasm_path(), :add, [5, 3])
Happy WASM coding! 🚀
""")
end
defp write_mix_exs(path, app_name, module_name, opts) do
sup_line = if opts[:sup], do: ",\n mod: {#{module_name}.Application, []}", else: ""
content = """
defmodule #{module_name}.MixProject do
use Mix.Project
def project do
[
app: :#{app_name},
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger]#{sup_line}
]
end
defp deps do
[
{:firebird, "~> 0.1.0"}
]
end
end
"""
File.write!(Path.join(path, "mix.exs"), content)
Mix.shell().info(" Created #{path}/mix.exs")
end
defp write_lib(path, app_name, module_name, opts) do
# Main module
main = """
defmodule #{module_name} do
@moduledoc \"\"\"
#{module_name} - powered by Firebird WASM.
\"\"\"
end
"""
File.write!(Path.join(path, "lib/#{app_name}.ex"), main)
# Application module (if --sup)
if opts[:sup] do
app = """
defmodule #{module_name}.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# Add your WASM modules here:
# #{module_name}.Math,
# {Firebird.Pool, wasm: "wasm/math.wasm", size: 4, name: :math_pool}
]
opts = [strategy: :one_for_one, name: #{module_name}.Supervisor]
Supervisor.start_link(children, opts)
end
end
"""
File.write!(Path.join(path, "lib/#{app_name}/application.ex"), app)
end
# Example WASM module wrapper
example = """
defmodule #{module_name}.Math do
@moduledoc \"\"\"
Example WASM math module.
Replace "wasm/math.wasm" with your actual WASM file.
## Usage
{:ok, [8]} = #{module_name}.Math.add(5, 3)
[55] = #{module_name}.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!(Path.join(path, "lib/#{app_name}/math.ex"), example)
Mix.shell().info(" Created lib/ files")
end
defp write_test(path, app_name, module_name) do
helper = """
ExUnit.start()
"""
File.write!(Path.join(path, "test/test_helper.exs"), helper)
test = """
defmodule #{module_name}.MathTest do
use ExUnit.Case
# To run these tests, place a math.wasm in wasm/
# with add/2, multiply/2, and fibonacci/1 exports.
@tag :skip
test "add" do
{:ok, pid} = #{module_name}.Math.start_link()
assert {:ok, [8]} = #{module_name}.Math.add(5, 3)
#{module_name}.Math.stop()
end
end
"""
File.write!(Path.join(path, "test/#{app_name}_test.exs"), test)
Mix.shell().info(" Created test/ files")
end
defp write_config(path) do
File.mkdir_p!(Path.join(path, "config"))
config = """
import Config
# Firebird configuration (optional - sensible defaults are used)
# config :firebird,
# wasm_path: "wasm",
# memory_limit: 67_108_864,
# timeout: 5_000
"""
File.write!(Path.join(path, "config/config.exs"), config)
end
defp write_gitignore(path) do
content = """
/_build/
/cover/
/deps/
/doc/
/.fetch
erl_crash.dump
*.ez
#{Path.basename(path)}-*.tar
/tmp/
/native/target/
"""
File.write!(Path.join(path, ".gitignore"), content)
end
defp write_readme(path, _app_name, module_name) do
content = """
# #{module_name}
A WASM-powered Elixir project using [Firebird](https://github.com/hdresearch/firebird).
## Setup
```bash
mix deps.get
```
## Usage
Place your `.wasm` files in the `wasm/` directory, then:
```elixir
# One-shot
{:ok, [8]} = Firebird.run("wasm/math.wasm", :add, [5, 3])
# Or use the module wrapper
{:ok, [8]} = #{module_name}.Math.add(5, 3)
```
## Building WASM Modules
### Rust
```bash
cd native
./build.sh
```
### Any language
Any language that compiles to WebAssembly will work. Just place the
`.wasm` file in `wasm/` and call it with Firebird.
"""
File.write!(Path.join(path, "README.md"), content)
end
defp write_rust_scaffold(path) do
native_path = Path.join(path, "native")
File.mkdir_p!(Path.join(native_path, "src"))
cargo = """
[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!(Path.join(native_path, "Cargo.toml"), cargo)
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, mut b) = (0i32, 1i32);
for _ in 2..=n {
let tmp = b;
b = a + b;
a = tmp;
}
b
}
"""
File.write!(Path.join(native_path, "src/lib.rs"), lib_rs)
build_sh = """
#!/bin/bash
set -e
cd "$(dirname "$0")"
cargo build --target wasm32-unknown-unknown --release
mkdir -p ../wasm
cp target/wasm32-unknown-unknown/release/wasm_native.wasm ../wasm/
echo "✅ Built wasm/wasm_native.wasm"
"""
build_path = Path.join(native_path, "build.sh")
File.write!(build_path, build_sh)
File.chmod!(build_path, 0o755)
Mix.shell().info(" Created native/ Rust scaffold")
end
end