ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Gleam 子目录 FFI 支持验证:深入解读 subdir_ffi 测试包的跨目标原生模块机制

Gleam 子目录 FFI 支持验证:深入解读 subdir_ffi 测试包的跨目标原生模块机制 Gleam 子目录 FFI 支持验证深入解读 subdir_ffi 测试包的跨目标原生模块机制【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam在 Gleam 编译器中external注解允许 Gleam 代码直接调用 Erlang、Elixir 或 JavaScript 编写的原生模块实现 FFIForeign Function Interface外部函数接口。但当这些原生文件.mjs、.erl、.ex、.hrl被放置在src下的子目录中时编译器能否正确解析模块名与相对路径便成为一个需要专门验证的关键能力。本指南以仓库中 subdir_ffi 测试包 为蓝本逐步拆解其目录结构、external注解写法、.hrl头文件引用方式以及跨 Erlang、Node.js、Deno、Bun 四个运行时的测试驱动流程帮助读者掌握在嵌套目录中编写与验证 Gleam FFI 模块的完整实战方法。subdir_ffi 测试包的定位test/subdir_ffi/README.md 用两句话界定了这个包的用途This package is used to check if the compiler properly supports FFI files (such as.mjs,.erland.hrl) in subdirectories. Test across all targets and runtimes by runningmake.它并非一个面向终端用户的功能示例而是 Gleam 编译器自带的集成测试夹具test fixture。其核心命题是当 FFI 文件位于src的子目录中时编译器必须能够正确完成三件事正确解析 Gleam 源文件中的external注解将外部模块名与相对路径映射到实际文件正确生成 Erlang 目标.erl/.ex/.hrl与 JavaScript 目标.mjs所对应的调用代码在所有受支持的目标平台与运行时上保持行为一致不因目录层级而失效。因此README 末尾特别强调Test across all targets and runtimes by runningmake——这个包的价值是通过自动化测试来持续回归验证编译器在子目录 FFI 上的正确性。目录结构与文件职责在开始分析代码之前先给出该包在仓库中的完整目录布局test/subdir_ffi/ ├── Makefile ├── README.md ├── gleam.toml ├── manifest.toml ├── src/ │ ├── project.gleam # 顶层模块入口 │ ├── project_ffi.erl # 顶层 Erlang FFI │ ├── project_ffi.mjs # 顶层 JavaScript FFI │ ├── headers/ │ │ └── submodule_ffi_header.hrl # 嵌套目录中的 Erlang 头文件 │ └── nested/ │ ├── submodule.gleam # 嵌套 Gleam 子模块 │ ├── submodule_ffi.erl # 嵌套 Erlang FFI │ ├── submodule_ffi.ex # 嵌套 Elixir FFI │ └── submodule_ffi.mjs # 嵌套 JavaScript FFI └── test/ └── subdir_ffi_test.gleam # 测试入口从布局可以清晰看出测试设计的层次性顶层src/根目录project.gleam与同目录下的project_ffi.erl、project_ffi.mjs构成普通 FFI基线一层嵌套src/nested/submodule.gleam与同目录的submodule_ffi.*构成子目录 FFI的核心被测场景深层嵌套src/headers/submodule_ffi_header.hrl用于验证子目录中的.hrl头文件能否被更深的目录层级正确-include。external注解跨目标 FFI 的声明语法Gleam 通过external注解为函数声明指定外部实现其语法针对每个编译目标分别给出模块与函数名。观察 src/project.gleam 的声明方式external(erlang, project_ffi, log) external(javascript, ./project_ffi.mjs, log) fn println(a: String) - Nil external(erlang, submodule_ffi, main2) external(javascript, ./nested/submodule_ffi.mjs, main2) fn subdir_message() - String external(erlang, Elixir.ElixirFileAgain, main) external(javascript, ./nested/submodule_ffi.mjs, main2) fn subdir_elixir_message() - String这里包含了两种典型的注解形态Erlang/Elixir 目标使用模块名module name而非文件路径。project_ffi与submodule_ffi分别对应 src/project_ffi.erl 和 src/nested/submodule_ffi.erl 中-module(...)声明的模块名与文件所在目录无关。当目标是 Elixir 模块时则使用Elixir.ElixirFileAgain这种带Elixir.前缀的原子形式对应 src/nested/submodule_ffi.ex 中defmodule ElixirFileAgain的定义。JavaScript 目标使用相对于当前 Gleam 源文件的相对路径。顶层文件写./project_ffi.mjs而嵌套在nested/目录中的 Gleam 模块则需要写./nested/submodule_ffi.mjs。这一差异正是子目录 FFI问题的关键所在Erlang 侧靠模块名解耦JavaScript 侧靠路径直接关联文件。值得注意的细节是subdir_elixir_message在 Erlang 目标上调用 Elixir 模块ElixirFileAgain在 JavaScript 目标上却复用./nested/submodule_ffi.mjs的main2导出。这说明同一个 Gleam 函数可以为不同目标声明完全不同的外部实现这也是跨运行时一致性测试的基础。嵌套子模块中的 FFI 调用链更深层的验证发生在嵌套模块内部。src/nested/submodule.gleam 展示了子目录中的 Gleam 模块如何同时调用自己的 FFI和上级目录的 FFIpub fn submodule_main() { parent_println(message()) parent_println(elixir_message()) } external(erlang, project_ffi, log) external(javascript, ../project_ffi.mjs, log) fn parent_println(a: String) - Nil external(erlang, submodule_ffi, main) external(javascript, ./submodule_ffi.mjs, main) fn message() - String external(erlang, Elixir.ElixirFile, main) external(javascript, ./submodule_ffi.mjs, main) fn elixir_message() - String这段代码验证了子目录场景下的三种路径解析场景Erlang 目标JavaScript 目标验证点调用父目录 FFIproject_ffi模块名../project_ffi.mjs相对路径向上跨目录的相对路径解析调用同级 FFIsubmodule_ffi模块名./submodule_ffi.mjs相对路径同目录相对路径解析调用 Elixir 模块Elixir.ElixirFile复用同级.mjs同一函数的多目标实现JavaScript 侧的../project_ffi.mjs是一个极佳的回归测试用例它验证编译器在生成 import 语句时能够基于 Gleam 源文件所在目录nested/正确上溯一级定位父目录中的 FFI 文件而不是错误地按模块名或根目录拼接路径。对应的原生实现src/nested/submodule_ffi.erl 与 src/nested/submodule_ffi.mjs也刻意保持了功能上的对等Erlang 侧导出main/0与main2/0两个函数JavaScript 侧导出main与main2两个函数确保两个目标都能被独立调用与断言。.hrl头文件嵌套目录中的 Erlang 预处理器支持src/headers/submodule_ffi_header.hrl 是测试包中层级最深的一个文件其内容是一个简单的 Erlang 头文件函数定义header_function() - Hello, from the nested Erlang header!.它被 src/nested/submodule_ffi.erl 通过预处理器指令引用-include(../headers/submodule_ffi_header.hrl).这里同样采用了相对于 FFI 文件自身所在目录的相对路径nested/目录中的.erl文件向上引用headers/目录中的.hrl。main/0与main2/0都调用header_function()来拼接返回值因此只要 Erlang 目标编译通过并运行成功就同时证明了编译器会将src/下的.hrl文件纳入编译输入子目录中的.erl文件可以通过相对路径-include深层目录中的头文件.hrl中的代码可以被正确编译进最终的 BEAM 字节码。这补全了 README 中列出的三种扩展名.mjs、.erl、.hrl中的最后一环再加上.exElixir 源文件五种原生文件类型在子目录场景下均得到了覆盖。顶层 FFI 与 Elixir 模块顶层目录同样保留了完整的 FFI 参考实现。src/project_ffi.mjs 提供log导出src/project_ffi.erl 提供log/1函数在 project.gleam 中被println引用。而 src/nested/submodule_ffi.ex 定义了ElixirFile与ElixirFileAgain两个模块分别被submodule.gleam的elixir_message和project.gleam的subdir_elixir_message调用defmodule ElixirFile do def main() do Hello, from the Elixir module! end end defmodule ElixirFileAgain do def main() do Hello, from another Elixir module! end end这里有一个值得指出的细节.ex文件同样位于nested/子目录中但 Gleam 侧引用它时使用的Elixir.ElixirFile是全局唯一的模块原子不包含任何目录信息——这再次印证 Erlang/Elixir 生态的 FFI 寻址完全基于模块名而非路径。测试入口与多运行时驱动测试入口 test/subdir_ffi_test.gleam 极简它只是导入并调用project.main()让真实的跨 FFI 调用链包括嵌套模块的打印与返回值拼接在运行时完整执行import project pub fn main() { project.main() }实际的行为断言分散在各 FFI 模块的返回字符串中如Hello, from the nested Erlang module!测试运行时会检查输出与退出码。这一设计说明子目录 FFI 的验证重点是端到端可运行性——只要任一路径解析出错编译或运行阶段就会失败。驱动这一切的是 test/subdir_ffi/Makefile它通过一条make命令串联全部目标与运行时.PHONY: build build: clean erlang nodejs deno bun .PHONY: clean clean: rm -rf build .PHONY: erlang erlang: echo test/subdir_ffi on Erlang cargo run --quiet -- test --target erlang .PHONY: nodejs nodejs: echo test/subdir_ffi on JavaScript with Node cargo run --quiet -- test --target javascript --runtime nodejs .PHONY: deno deno: echo test/subdir_ffi on JavaScript with Deno cargo run --quiet -- test --target javascript --runtime deno .PHONY: bun bun: echo test/subdir_ffi on JavaScript with Bun cargo run --quiet -- test --target javascript --runtime bunMakefile 揭示了几条重要信息它通过cargo run调用编译器本体而非安装好的gleam可执行文件意味着该测试运行的是仓库中正在开发的源码版本用于验证最新编译器对子目录 FFI 的支持四个测试目标覆盖两种编译目标、三种 JavaScript 运行时Erlang 原生运行时、Node.js、Deno、Bun先clean再构建保证每次测试都从干净的build目录开始避免残留产物掩盖路径解析问题JavaScript 目标的运行时通过--runtime参数切换而 Erlang 目标无需指定运行时。因此 README 中的Test across all targets and runtimes by runningmake可以精确对应为make等价于依次执行erlang、nodejs、deno、bun四个子目标。编译链路中的底层机制佐证子目录 FFI 之所以值得专门测试是因为编译器内部对两种目标的处理路径截然不同。从 compiler-core/src/erlang.rs 的源码可以看到Erlang 目标对带external注解的函数会直接生成对外部模块的远程调用remote call——例如源码注释中提到的external(erlang, io, format)这种形态module与external_function_name被提取出来构造调用而文件本身的位置信息并不参与寻址这解释了为什么 Erlang 侧可以完全无视目录层级。而 JavaScript 目标的 import 生成由 compiler-core/src/javascript/import.rs 负责其注释明确说明该类负责收集 external functions, to be rendered into a JavaScript module——即把external(javascript, ...)中给出的路径拼装为 ES module 的 import 语句。相对路径./、../的解析正确性正是subdir_ffi回归测试的覆盖重点。配置与依赖该测试包的 gleam.toml 声明了两个依赖gleam_stdlib 0.34.0 and 2.0.0运行时依赖提供标准库gleeunit 1.0.0 and 2.0.0开发依赖测试框架。对应的 manifest.toml 锁定了具体版本gleam_stdlib 0.40.0与gleeunit 1.2.0二者均来自 Hex 源。对于想在自己项目中复现本测试的开发者可以参照这一配置建立同等的最小依赖集合。小结把子目录 FFI 测试迁移到自己的项目subdir_ffi包虽小却完整覆盖了子目录 FFI 的全部关键维度。在你自己维护的 Gleam 项目中可以按照如下清单复刻这套验证布局在src/下建立至少一层的子目录将.gleam模块与其 FFI 文件.mjs/.erl/.ex放在同一目录JavaScript 路径在external(javascript, ...)中使用相对于当前 Gleam 文件的路径./指向同级../指向父级Erlang 模块名external(erlang, ...)使用-module声明的模块名与文件位置无关Elixir 模块则用Elixir.ModuleName原子头文件.hrl可以放在任意子目录.erl中通过-include(../relative/path.hrl)引用回归驱动参考该包的 Makefile用make串联erlang、nodejs、deno、bun四个目标确保任意目录结构调整都不会破坏 FFI 解析。通过这套方法你可以像 Gleam 编译器团队一样用自动化手段持续保障嵌套目录原生模块在全部目标与运行时上的行为一致性。【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表