Current section
Files
Jump to
Current section
Files
lib/mix/tasks/firebird.target.watch.ex
defmodule Mix.Tasks.Firebird.Target.Watch do
@moduledoc """
Watch for file changes and automatically recompile to WASM.
Monitors source directories for changes and triggers incremental
WASM compilation when files are modified. Uses content hashing
to avoid unnecessary recompilation.
## Usage
# Watch with default settings
mix firebird.target.watch
# Watch specific directories
mix firebird.target.watch --dirs lib/wasm_modules
# Watch with optimizations enabled
mix firebird.target.watch --optimize --tco
# Custom output directory
mix firebird.target.watch --output priv/wasm
# Custom poll interval (milliseconds)
mix firebird.target.watch --interval 500
## How It Works
1. Performs an initial compilation of all stale sources
2. Polls source directories for file changes every second (configurable)
3. Uses content hashing to detect actual changes (not just mtime)
4. Only recompiles files that actually changed
5. Reports compilation results in real-time
Press Ctrl+C to stop watching.
"""
use Mix.Task
@shortdoc "Watch for changes and recompile to WASM"
@switches [
output: :string,
dirs: :string,
files: :string,
optimize: :boolean,
tco: :boolean,
inline: :boolean,
wat_only: :boolean,
verbose: :boolean,
verify: :boolean,
interval: :integer
]
@aliases [
o: :output,
d: :dirs,
v: :verbose,
i: :interval
]
@impl Mix.Task
def run(args) do
{opts, _rest, _invalid} = OptionParser.parse(args, switches: @switches, aliases: @aliases)
interval = Keyword.get(opts, :interval, 1000)
Mix.shell().info("🔥 Firebird WASM Target — Watch Mode")
Mix.shell().info(" Poll interval: #{interval}ms")
Mix.shell().info(" Press Ctrl+C to stop")
Mix.shell().info("")
# Initial compilation
Mix.shell().info("📦 Initial compilation...")
compile_opts = Keyword.delete(opts, :interval)
Mix.Tasks.Firebird.Target.Compile.run(build_args(compile_opts))
Mix.shell().info("")
Mix.shell().info("👀 Watching for changes...")
Mix.shell().info("")
# Start watch loop
watch_loop(compile_opts, snapshot(compile_opts), interval)
end
defp watch_loop(opts, prev_snapshot, interval) do
Process.sleep(interval)
current = snapshot(opts)
changed = detect_changes(prev_snapshot, current)
if Enum.any?(changed) do
Mix.shell().info("")
timestamp = Calendar.strftime(DateTime.utc_now(), "%H:%M:%S")
Mix.shell().info("🔄 [#{timestamp}] Changes detected in #{length(changed)} file(s):")
Enum.each(changed, fn path ->
Mix.shell().info(" #{Path.relative_to_cwd(path)}")
end)
Mix.shell().info("")
# Recompile only changed files
file_args = ["--files", Enum.join(changed, ",")]
extra_args = build_args(Keyword.drop(opts, [:files, :dirs]))
Mix.Tasks.Firebird.Target.Compile.run(file_args ++ extra_args)
Mix.shell().info("")
Mix.shell().info("👀 Watching for changes...")
watch_loop(opts, current, interval)
else
watch_loop(opts, prev_snapshot, interval)
end
end
defp snapshot(opts) do
sources = discover_sources(opts)
Map.new(sources, fn path ->
hash =
case File.read(path) do
{:ok, content} -> :crypto.hash(:sha256, content)
_ -> nil
end
{path, hash}
end)
end
defp detect_changes(prev, current) do
# Files that are new or have different content hashes
Enum.flat_map(current, fn {path, hash} ->
case Map.get(prev, path) do
^hash -> []
_ -> [path]
end
end)
end
defp discover_sources(opts) do
dirs =
case Keyword.get(opts, :dirs) do
nil -> ["lib"]
dirs_str -> String.split(dirs_str, ",", trim: true)
end
files =
case Keyword.get(opts, :files) do
nil -> []
files_str -> String.split(files_str, ",", trim: true)
end
explicit = Enum.filter(files, &File.regular?/1)
discovered =
dirs
|> Enum.flat_map(fn dir ->
if File.dir?(dir) do
Path.wildcard(Path.join(dir, "**/*.{ex,exs}"))
else
[]
end
end)
|> Enum.filter(fn path ->
case File.read(path) do
{:ok, content} ->
String.contains?(content, "@wasm true") or
String.contains?(path, "wasm_modules")
_ ->
false
end
end)
(explicit ++ discovered) |> Enum.sort() |> Enum.uniq()
end
defp build_args(opts) do
Enum.flat_map(opts, fn
{:output, val} -> ["--output", val]
{:dirs, val} -> ["--dirs", val]
{:files, val} -> ["--files", val]
{:optimize, true} -> ["--optimize"]
{:tco, true} -> ["--tco"]
{:inline, true} -> ["--inline"]
{:wat_only, true} -> ["--wat-only"]
{:verbose, true} -> ["--verbose"]
{:verify, true} -> ["--verify"]
_ -> []
end)
end
end