Current section
Files
Jump to
Current section
Files
lib/firebird/target/config.ex
defmodule Firebird.Target.Config do
@moduledoc """
Configuration for the Firebird WASM compilation target.
Holds all settings needed to discover, compile, and output WASM modules
from Elixir source files.
## Creating a Config
# From keyword options
config = Firebird.Target.Config.new(
output_dir: "_build/wasm",
sources: ["lib/wasm_modules"],
optimize: true
)
# From Mix project config
config = Firebird.Target.Config.from_mix_project()
# Merge CLI options on top of project config
config = Firebird.Target.Config.from_mix_project()
|> Firebird.Target.Config.merge(cli_opts)
## Options
- `:output_dir` - Directory for compiled WASM output (default: `"_build/wasm"`)
- `:sources` - List of source directories to scan (default: `["lib"]`)
- `:files` - Explicit list of files to compile (overrides source scanning)
- `:optimize` - Enable IR optimizations like constant folding (default: `false`)
- `:tco` - Enable tail call optimization (default: `false`)
- `:inline` - Enable function inlining (default: `false`)
- `:cse` - Enable common subexpression elimination (default: `false`)
- `:licm` - Enable loop-invariant code motion (default: `false`)
- `:wat_only` - Only generate WAT text, skip binary compilation (default: `false`)
- `:verify` - Load and verify compiled WASM modules (default: `false`)
- `:source_map` - Generate source maps (default: `false`)
- `:verbose` - Enable verbose output (default: `false`)
- `:force` - Force recompilation of all files (default: `false`)
"""
@type t :: %__MODULE__{
output_dir: String.t(),
sources: [String.t()],
files: [String.t()] | nil,
optimize: boolean(),
tco: boolean(),
inline: boolean(),
cse: boolean(),
licm: boolean(),
wat_only: boolean(),
verify: boolean(),
source_map: boolean(),
verbose: boolean(),
force: boolean()
}
defstruct output_dir: "_build/wasm",
sources: ["lib"],
files: nil,
optimize: false,
tco: false,
inline: false,
cse: false,
licm: false,
wat_only: false,
verify: false,
source_map: false,
verbose: false,
force: false
@doc """
Create a new Config from keyword options.
## Examples
config = Config.new(output_dir: "priv/wasm", optimize: true)
config = Config.new([]) # all defaults
"""
@spec new(keyword()) :: t()
def new(opts \\ []) when is_list(opts) do
struct(__MODULE__, opts)
end
@doc """
Create a Config from the current Mix project's `:firebird` configuration.
Reads from `mix.exs`:
def project do
[
firebird: [
wasm_output: "_build/wasm",
wasm_sources: ["lib/wasm_modules"],
optimize: true
]
]
end
"""
@spec from_mix_project() :: t()
def from_mix_project do
config = get_project_firebird_config()
new(
output_dir: Keyword.get(config, :wasm_output, "_build/wasm"),
sources: Keyword.get(config, :wasm_sources, ["lib"]),
optimize: Keyword.get(config, :optimize, false),
tco: Keyword.get(config, :tco, false),
inline: Keyword.get(config, :inline, false),
cse: Keyword.get(config, :cse, false),
licm: Keyword.get(config, :licm, false),
wat_only: Keyword.get(config, :wat_only, false),
verify: Keyword.get(config, :verify, false),
source_map: Keyword.get(config, :source_map, false)
)
end
@doc """
Merge additional options into an existing config.
CLI options override existing config values. Only non-nil values
from `opts` are merged.
## Examples
config = Config.from_mix_project()
config = Config.merge(config, output_dir: "custom/output", verbose: true)
"""
@spec merge(t(), keyword()) :: t()
def merge(%__MODULE__{} = config, opts) when is_list(opts) do
# Only merge non-nil values
filtered =
opts
|> Enum.filter(fn {_k, v} -> v != nil end)
struct(config, filtered)
end
@doc """
Validate the config and return a list of warnings.
Checks for common misconfigurations like nonexistent directories
or missing prerequisites.
"""
@spec validate(t()) :: {:ok, [String.t()]}
def validate(%__MODULE__{} = config) do
warnings = []
# Check output dir parent exists
warnings =
if String.starts_with?(config.output_dir, "/") and
not File.dir?(Path.dirname(config.output_dir)) do
["Output directory parent doesn't exist: #{Path.dirname(config.output_dir)}" | warnings]
else
warnings
end
# Check source dirs exist
warnings =
Enum.reduce(config.sources || [], warnings, fn dir, acc ->
if not File.dir?(dir) do
["Source directory not found: #{dir}" | acc]
else
acc
end
end)
# Check explicit files exist
warnings =
Enum.reduce(config.files || [], warnings, fn file, acc ->
if not File.regular?(file) do
["Source file not found: #{file}" | acc]
else
acc
end
end)
# Check wat2wasm availability
warnings =
if not config.wat_only and not Firebird.Compiler.wat2wasm_available?() do
["wat2wasm not found - binary compilation will fail" | warnings]
else
warnings
end
{:ok, Enum.reverse(warnings)}
end
defp get_project_firebird_config do
case Mix.Project.config() do
config when is_list(config) -> Keyword.get(config, :firebird, [])
_ -> []
end
rescue
_ -> []
end
end