Packages
Elixir library for working with Linux nftables rules. Provides high-level APIs for building tables, chains, rules, maps and sets. Works with NFTables.Port for communicating with the kernel firewall.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/compile/module_indexer.ex
defmodule Mix.Tasks.Compile.ModuleIndexer do
@moduledoc """
Custom Mix compiler that generates NFTables.ExprIndex module.
This compiler runs after the standard Elixir compiler and:
1. Queries all compiled modules in the :nftables application
2. Filters for modules matching NFTables.Expr.* prefix
3. Generates and compiles NFTables.ExprIndex with all() function
The generated module is used by the `use NFTables` macro to
dynamically import all expression building modules.
"""
use Mix.Task.Compiler
@recursive true
@manifest ".compile.module_indexer"
@doc """
Runs the module indexer compiler.
Returns:
- `{:ok, []}` on success with changes
- `{:noop, []}` when no changes detected
"""
def run(_args) do
# Discover all NFTables.Expr.* modules
expr_modules = discover_expr_modules()
# Check if module list changed (compare hash)
if modules_changed?(expr_modules) do
# Generate and compile ExprIndex module
generate_expr_index(expr_modules)
# Write manifest with hash
write_manifest(expr_modules)
Mix.shell().info("Generated NFTables.ExprIndex with #{length(expr_modules)} modules")
{:ok, []}
else
# No changes, skip regeneration
{:noop, []}
end
end
@doc """
Cleans up generated files.
"""
def clean do
File.rm(@manifest)
File.rm("lib/nftables_expr_index.ex")
:ok
end
# Private functions
defp discover_expr_modules do
# Read modules from the .app file generated by elixir compiler
app_file = Mix.Project.app_path() <> "/ebin/#{Mix.Project.config()[:app]}.app"
if File.exists?(app_file) do
# Read and parse the .app file
{:ok, [{:application, _app, properties}]} = :file.consult(String.to_charlist(app_file))
# Extract modules list
modules = Keyword.get(properties, :modules, [])
# Filter for NFTables.Expr.* modules
modules
|> Enum.filter(&expr_module?/1)
|> Enum.sort()
else
# .app file doesn't exist yet (very first compile)
[]
end
end
defp expr_module?(module) do
module_string = to_string(module)
# Match NFTables.Expr.* but exclude NFTables.Expr itself and internal modules
String.starts_with?(module_string, "Elixir.NFTables.Expr.") and
module_string != "Elixir.NFTables.Expr" and
not String.ends_with?(module_string, ".Structs")
end
defp modules_changed?(current_modules) do
current_hash = compute_hash(current_modules)
case read_manifest() do
{:ok, previous_hash} ->
current_hash != previous_hash
:error ->
# No manifest exists, first run
true
end
end
defp compute_hash(modules) do
:erlang.phash2(modules)
end
defp generate_expr_index(modules) do
# Generate module code as a string
module_code = """
defmodule NFTables.ExprIndex do
@moduledoc \"\"\"
Auto-generated module index for NFTables.Expr modules.
This module is generated at compile time by Mix.Tasks.Compile.ModuleIndexer.
DO NOT EDIT - regenerated on each compilation when module list changes.
Contains #{length(modules)} modules.
\"\"\"
@doc \"\"\"
Returns list of all NFTables.Expr submodules.
\"\"\"
def all do
#{inspect(modules)}
end
end
"""
# Write to file
output_path = "lib/nftables_expr_index.ex"
File.write!(output_path, module_code)
:ok
end
defp write_manifest(modules) do
hash = compute_hash(modules)
content = :erlang.term_to_binary(hash)
File.write!(@manifest, content)
end
defp read_manifest do
if File.exists?(@manifest) do
content = File.read!(@manifest)
hash = :erlang.binary_to_term(content)
{:ok, hash}
else
:error
end
end
end