编译时通过宏获取指定命名空间模块列表返回undefined问题
问题原因
编译时调用:application.get_key返回:undefined,核心原因有两点:
- 应用未加载/启动:
:application.get_key是用于查询已启动或已加载的Erlang/Elixir应用的元数据接口。而在模块编译阶段,:my_app的.app元数据文件通常还未生成(该文件在编译完成后才会输出),自然无法获取有效数据。 - 编译顺序限制:Elixir按模块依赖顺序逐个编译,当你的schema模块执行
use MyModuleLoader时,应用内其他模块可能还未完成编译,即便能拿到模块列表,也会是不完整的。
解决方案
根据你“编译时获取指定命名空间模块列表”的需求,提供三种可行方案:
方案1:手动加载应用元数据(适用于编译后期/二次编译场景)
先手动加载应用元数据(无需启动应用),再获取模块列表。注意该方法仅在.app文件已生成时有效:
defmodule MyApp.MyModuleLoader do defmacro __using__(opts) do quote bind_quoted: [opts: opts] do app = :my_app # 加载应用元数据 :ok = :application.load(app) # 获取完整模块列表 {:ok, modules} = :application.get_key(app, :modules) # 过滤指定命名空间的模块(示例:MyApp.Schema下的模块) filtered_modules = Enum.filter(modules, fn module -> Atom.to_string(module) |> String.starts_with?("MyApp.Schema.") end) IO.puts("Filtered modules: #{Kernel.inspect(filtered_modules)}") end end end
方案2:扫描已加载的模块(适用于获取当前已编译完成的模块)
利用:code.all_loaded()获取当前虚拟机中已加载的模块,再过滤目标命名空间。该方法能拿到编译到当前阶段已完成的模块:
defmodule MyApp.MyModuleLoader do defmacro __using__(opts) do quote bind_quoted: [opts: opts] do # 获取所有已加载的模块 all_loaded = :code.all_loaded() |> Enum.map(&elem(&1, 0)) # 过滤目标命名空间 target_modules = Enum.filter(all_loaded, fn module -> module_str = Atom.to_string(module) String.starts_with?(module_str, "MyApp.Schema.") end) IO.puts("Loaded target modules: #{Kernel.inspect(target_modules)}") end end end
方案3:扫描项目源文件(最可靠的编译时完整列表)
如果需要获取所有应被编译的目标命名空间模块(无论是否已编译),可以直接扫描项目源文件目录,解析模块名:
defmodule MyApp.MyModuleLoader do defmacro __using__(opts) do quote bind_quoted: [opts: opts] do # 获取项目lib目录路径 src_root = Path.join(Mix.Project.app_path(), "lib") # 目标命名空间对应的目录(示例:my_app/schema) target_dir = Path.join(src_root, "my_app/schema") target_modules = Path.wildcard(Path.join(target_dir, "**/*.ex")) |> Enum.map(fn path -> # 从文件路径转换为模块名 path |> String.replace_prefix(src_root <> "/", "") |> String.replace_suffix(".ex", "") |> String.replace("/", ".") |> String.to_atom() end) IO.puts("All target modules: #{Kernel.inspect(target_modules)}") end end end
注意事项
- 若需要100%完整的模块列表,建议在编译完成后执行逻辑(比如通过Mix编译钩子任务),因为编译阶段无法保证所有模块都已处理完成。
- 方案3依赖“模块名与目录结构一致”的约定,若你的项目不符合该约定,需要调整路径解析逻辑。
内容的提问来源于stack exchange,提问作者mondichuk
相关产品推荐
相关产品推荐

