深入解析 Gleam 的 `gleam run --module` 模块运行机制:基于 test/running_modules 集成测试的完整实战指南
深入解析 Gleam 的gleam run --module模块运行机制基于 test/running_modules 集成测试的完整实战指南【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam本篇技术指南围绕 Gleam 编译器仓库中的 test/running_modules/README.md 展开系统梳理gleam run -m即--module在 Erlang 与 JavaScriptNode.js / Deno目标下的模块定位、默认入口选择、参数校验与错误处理等行为。文章以该测试目录中的 run_tests.sh 为骨架结合 compiler-cli/src/run.rs 的源码实现帮助你彻底掌握「运行任意模块」「运行依赖包内模块」「在 test/ 与 dev/ 目录中运行模块」等实战能力并理解其中的边界条件与设计动机。一、关联文档定位一份「可运行的规格说明」test/running_modules目录是一组面向 Gleam 编译器自身的集成测试夹具fixture其 README 全文虽然只有两句话却精确概括了被测对象Tests running modules with thegleam run -mcommand on all targets and runtimes. Thetestdirectory is required for gleeunit to run in the running a dependency module tests.翻译过来即该目录用于在所有编译目标target与运行时runtime上测试gleam run -m命令同时test/目录的存在是 gleeunit 能够运行依赖模块测试的前提条件详见下文第六节。测试项目名为module见 gleam.toml 中的name module依赖gleam_stdlib与gleeunit因此本节所有命令示例中的默认模块名均与包名module相关。这个目录在仓库中承担着行为规格的角色任何修改run命令相关逻辑的提交都必须先让这里的脚本通过。理解它的测试矩阵就等于理解了gleam run --module的完整契约。二、被测项目的目录布局src / test / dev 三区test/running_modules的目录结构刻意覆盖了 Gleam 项目的三种源码区域test/running_modules/ ├── gleam.toml # name module依赖 gleam_stdlib、gleeunit ├── Makefile # 入口make test-all ├── run_tests.sh # 核心测试脚本全部断言都在这里 ├── src/ # 主源码区 │ ├── module.gleam # pub fn main()顶层模块 │ └── module/ │ ├── sub_module.gleam # 嵌套模块pub fn main() │ ├── no_main_function.gleam # 空模块没有 main │ └── wrong_arity.gleam # pub fn main(arg: Int)参数个数错误 ├── test/ # 测试源码区 │ ├── module_test.gleam # 默认 test 模块module_test │ ├── module_in_test.gleam # pub fn main() │ ├── wrong_test_arity.gleam # main 带参数用于断言失败 │ └── nested/ │ └── module_in_test.gleam # test/ 下的嵌套模块 └── dev/ # dev 源码区gleam dev 专用 ├── module_dev.gleam # 默认 dev 模块module_dev ├── module_in_dev.gleam # pub fn main() ├── wrong_dev_arity.gleam # main 带参数用于断言失败 └── nested/ └── module_in_dev.gleam # dev/ 下的嵌套模块每个区域都精心设计了三类用例模块可正常运行pub fn main()、缺少 main 函数、main 函数参数个数错误arity从而让测试脚本能够分别验证成功路径与失败路径。三、核心命令gleam run --module module的完整用法run_tests.sh先定义了should_succeed与should_fail两个辅助函数前者要求命令退出码为 0否则报错退出后者要求命令非零退出即预期失败。随后用它们组成了覆盖三种目标/运行时组合的测试矩阵目标 (target)运行时 (runtime)说明erlang无Erlang 不支持--runtime由erl执行javascriptnodejs由node执行javascriptdeno由deno执行以下命令均可直接复制到任意 Gleam 项目中验证# 1) 不指定模块默认运行 src/ 下与包名同名的模块module gleam run --target erlang gleam run --target javascript --runtime nodejs gleam run --target javascript --runtime deno # 2) 显式运行本包顶层模块 gleam run --module module --target erlang gleam run --module module --target javascript --runtime nodejs gleam run --module module --target javascript --runtime deno # 3) 运行嵌套模块模块路径用 / 分隔 gleam run --module module/sub_module --target erlang gleam run --module module/sub_module --target javascript --runtime nodejs gleam run --module module/sub_module --target javascript --runtime deno # 4) 运行依赖包gleeunit内的模块 gleam run --module gleeunit --target erlang gleam run --module gleeunit --target javascript --runtime nodejs gleam run --module gleeunit --target javascript --runtime deno # 5) 运行 test/ 与 dev/ 中的模块 gleam run --module module_in_test --target erlang gleam run --module nested/module_in_test --target erlang gleam run --module module_in_dev --target erlang gleam run --module nested/module_in_dev --target erlang默认模块名的选择规则当省略--module时运行哪个模块取决于命令类别。这一点在 compiler-cli/src/run.rs 中有明确的源码实现其中Which枚举区分了三种命令来源pub enum Which { Src, // gleam run Test, // gleam test Dev, // gleam dev } let module module.unwrap_or(match which { Which::Src root_config.name.to_string(), // 包名如 module Which::Test format!({}_test, root_config.name), // 包名_test如 module_test Which::Dev format!({}_dev, root_config.name), // 包名_dev如 module_dev });gleam run默认运行src/下与包名同名的模块本项目即modulegleam test默认运行module_test对应 test/module_test.gleamgleam dev默认运行module_dev对应 dev/module_dev.gleam。脚本中should_succeed test --target erlang、should_succeed dev --target erlang等用例正是验证了这两个默认入口可以被正常构建并执行。四、模块名合法性校验与 src/ 前缀拒绝gleam run --module对模块名有严格的语法校验。在 run.rs 中模块名必须匹配正则static IS_GLEAM_MODULE_PATTERN: OnceLockRegex OnceLock::new(); fn is_gleam_module(module: str) - bool { IS_GLEAM_MODULE_PATTERN .get_or_init(|| { Regex::new(format!( ^({module}{slash})*{module}$, module [a-z][_a-z0-9]*, slash /, )) .expect(is_gleam_module() RE regex) }) .is_match(module) }即模块名必须以小写字母开头后续可以是小写字母、下划线和数字且可自由嵌套如module/sub_module但不允许首尾出现/、连续斜杠或非法字符。仓库还附带了两组单测run.rs验证mod/name/、/mod/name、common-invalid-character等非法valid、valid/name合法。有意思的是失败用例should_fail run --module src/doesnt_exist脚本注释明确写着「Unknown module should only belong as a package or in src/ and test/」——即模块名中不允许带src/、test/、dev/目录前缀。虽然该命令也会因模块不存在而失败但源码揭示了更深一层的行为get_or_suggest_main_functionrun.rs在模块不存在时会尝试剥掉src/、test/、dev/前缀重新查找若去掉前缀后能找到合法模块则返回带suggestion建议的ModuleDoesNotExist错误——这就是为什么错误信息能提示你正确的模块名。for prefix in [src/, test/, dev/] { let other match module.strip_prefix(prefix) { Some(other) other.into(), None continue, }; if built.get_main_function(other, target).is_ok() { return Err(Error::ModuleDoesNotExist { module: EcoString::from(module), suggestion: Some(other), }); } }五、可运行模块的硬性要求pub 的零参数 main 函数gleam run --module对目标模块的入口函数有明确要求脚本用三个失败用例完整覆盖了这些边界# 未知模块 → 失败 should_fail run --module doesnt_exist # 模块存在但没有 main 函数 → 失败 should_fail run --module module/no_main_function # main 函数参数个数arity错误 → 失败 should_fail run --module module/wrong_arity should_fail run --module wrong_test_arity should_fail run --module wrong_dev_arity对应夹具源码清晰地展示了失败原因src/module/no_main_function.gleam除 SPDX 头外没有任何定义run时无法解析出mainsrc/module/wrong_arity.gleampub fn main(arg: Int) - Nil接受一个参数test/wrong_test_arity.gleam 与 dev/wrong_dev_arity.gleampub fn main(something)同样是带参形式。在 run.rs 中构建完成后会调用get_or_suggest_main_function查找module.main找不到或类型不符即返回错误只有公开pub、零参数、可执行的main才会被接受。这保证了被运行模块的入口在 Erlang 与 JavaScript 两端都能以统一方式调用见第七节。六、运行依赖包内的模块gleeunit 用例与Compile::DepsOnlyrun_tests.sh中最具启发性的用例是运行依赖包内的模块should_succeed run --module gleeunit --target erlang should_succeed run --module gleeunit --target javascript --runtime nodejs should_succeed run --module gleeunit --target javascript --runtime denogleeunit是 Gleam 的标准单元测试库本项目通过 gleam.toml 声明gleeunit 1.0.0 and 2.0.0。运行它说明--module不仅能运行根包模块还能运行任何已解析依赖中的模块。其底层机制在 run.rs 中CLI 会先通过find_package_config_for_module判断目标模块属于根包还是依赖据此设置编译策略compile: match package_kind { // 运行依赖模块时只编译依赖跳过根包的编译检查—— // 即使根包当前无法编译也能运行依赖的 main 函数 PackageKind::Dependency Compile::DepsOnly, PackageKind::Root Compile::All, }, root_target_support: match package_kind { PackageKind::Root TargetSupport::Enforced, // 根包必须适配当前目标 PackageKind::Dependency TargetSupport::NotEnforced, // 依赖只需自己可编译 },这是一个实用而精妙的设计当你要运行的是某个依赖包的模块时根包自身的编译状态不会阻塞你。这正解释了 README 中第二句话的含义——test/目录之所以是必须的是因为 gleeunit 自身的模块存在于依赖中运行其模块测试时需要一个标准的test/目录来承载测试用例与 gleeunit 的集成缺失该目录gleeunit 的测试运行流程便无从挂载。七、三种目标/运行时的底层执行原理run.rs为每种目标生成了不同的启动命令理解这些细节有助于排查 为什么模块这样被调用 的问题。Erlangerl -eval直接求值run_erlang_command 将 Gleam 模块路径中的/替换为 Erlang 命名空间分隔符如module/sub_module→modulesub_module随后通过erl的-eval求值erl -pa 每个包/ebin -eval modulemain:run(modulesub_module) -noshell -extra 程序参数其中-pa把build/dev/erlang/package/ebin加入代码路径-noshell跳过交互式 shell-extra之后的所有参数透传给程序本身。JavaScript生成临时入口文件再调用运行时Node.js 与 Bun 通过 write_javascript_entrypoint 在构建目录生成一个形如gleamprivate_main_v版本.mjs的入口文件内容为import { main } from ./module.mjs; main();然后分别执行node entry args或bun run entry args。Deno 则在 run_javascript_deno_command 中额外拼接权限参数——这正是测试项目 gleam.toml 里[javascript.deno] allow_read true发挥作用的地方配置文件中的allow_read、allow_net、allow_write、allow_env等字段会被翻译为deno run --allow-read...等标志unstable、location、allow_all等配置项也在此处生效。注意Erlang 目标不接受--runtime参数传入了会得到InvalidRuntime错误run.rs。八、如何在本地复现这套测试该测试目录随 Gleam 主仓库一并维护复现方式十分简单# 方式一通过顶层 Makefile在仓库根目录 cd test/running_modules make test-all # 等价于执行 ./run_tests.sh # 方式二直接运行脚本需已构建 gleam 二进制或使用 cargo ./run_tests.sh脚本内部通过cargo run -- args调用仓库内的 CLI 源码来执行每个用例should_succeed/should_fail会把命令输出重定向到/dev/null仅在断言失败时打印ERROR并以非零码退出从而保证 CI 中任一回归都能被立即捕获。你也可以手动逐条执行第三节中的命令观察module、sub module、Hello from test/、Hello from dev/等来自各夹具模块的输出如 src/module.gleam 打印top level module、test/module_in_test.gleam 打印Hello from test/、dev/module_in_dev.gleam 打印Hello from dev/直观验证模块解析的正确性。九、小结gleam run --module行为速查场景命令形态结果默认运行无--modulegleam run运行src/包名gleam test默认入口gleam test运行包名_testgleam dev默认入口gleam dev运行包名_dev运行嵌套模块--module module/sub_module用/分隔路径运行依赖包模块--module gleeunit仅编译依赖跳过根包检查模块名带src/前缀--module src/xxx失败并可能给出去前缀建议模块不存在--module doesnt_exist失败无main或 arity 错误—失败需pub fn main()零参数运行 test/ 内模块--module module_in_test成功可用于运行测试辅助模块运行 dev/ 内模块--module module_in_dev成功通过 test/running_modules/run_tests.sh 这份完整的测试矩阵配合 compiler-cli/src/run.rs 的源码实现你可以把这套模块运行契约完整迁移到自己的 Gleam 项目中无论是调试单个依赖包的入口、在test//dev/区域运行辅助模块还是理解 Deno 权限配置对run的影响都能做到有据可依、可测可验。【免费下载链接】gleam⭐️ A friendly language for building type-safe, scalable systems!项目地址: https://gitcode.com/GitHub_Trending/gl/gleam创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考