You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编译时通过宏获取指定命名空间模块列表返回undefined问题

问题原因

编译时调用:application.get_key返回:undefined,核心原因有两点:

  1. 应用未加载/启动::application.get_key是用于查询已启动或已加载的Erlang/Elixir应用的元数据接口。而在模块编译阶段,:my_app的.app元数据文件通常还未生成(该文件在编译完成后才会输出),自然无法获取有效数据。
  2. 编译顺序限制: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:02:38