mruby 的 mrbgems 扩展机制完整指南:从 Gem 接入、依赖管理到 C/Ruby 混合扩展
2026/9/23 17:52:39 网站建设 项目流程

mruby 的 mrbgems 扩展机制完整指南:从 Gem 接入、依赖管理到 C/Ruby 混合扩展

【免费下载链接】h2oH2O - the optimized HTTP/1, HTTP/2, HTTP/3 server项目地址: https://gitcode.com/gh_mirrors/h2/h2o

mrbgems 是 mruby 官方提供的库管理器,用于以标准化方式将 C 扩展与 Ruby 扩展集成进 mruby 构建。本文以 mrbgems.md 为主体,结合本仓库deps/mruby及其 mrbgems 目录中的真实代码,系统讲解如何在构建配置中接入第三方 Gem、如何用 GemBox 批量组合扩展、如何编写 C 扩展与 Ruby 扩展、如何管理依赖与冲突,以及如何构建可执行的二进制 Gem,帮助读者完整掌握 mruby 扩展开发与定制裁剪能力。

mrbgems 是什么

mrbgems 是 mruby 的库管理器,目标是把 C 语言扩展和 Ruby 语言扩展以统一、标准化的方式集成进 mruby。按惯例,每个 mrbgem 的名字都以mruby-前缀开头,例如提供Time类功能的 Gem 就命名为mruby-time

本仓库的 deps/mruby/mrbgems 目录下就内置了大量核心 Gem,如mruby-timemruby-iomruby-socketmruby-compiler,以及二进制类 Gemmruby-bin-mrubymruby-bin-mirbmruby-bin-mrbcmruby-bin-strip等;同时deps/下还捆绑了 H2O 实际使用的外部 mrbgem(如 mruby-digest、mruby-env、mruby-input-stream、mruby-onig-regexp 等)。这些 Gem 均遵循统一的mrbgem.rake规范文件组织,是理解 mrbgems 机制的最佳范例。

在构建配置中启用与添加 mrbgem

mrbgems 需要显式激活:在构建配置文件中调用conf.gem即可把某个 Gem 加入构建。

通过本地路径添加

conf.gem '/path/to/your/gem/dir'

也可以使用相对路径:

conf.gem 'examples/mrbgems/ruby_extension_example'

相对路径的解析规则取决于构建配置文件的位置:

  • 若构建配置文件位于build_config目录,则相对路径以MRUBY_ROOT为基准;
  • 否则,相对路径以构建配置文件所在目录为基准。

在 H2O 的 mruby 中,构建配置统一位于 deps/mruby/build_config 目录,其中 default.rb 是被 build_config.rb 推荐的默认配置入口,其注释中给出了多种conf.gem用法示例:

# conf.gem 'examples/mrbgems/ruby_extension_example' # conf.gem 'examples/mrbgems/c_extension_example' do |g| # g.cc.flags << '-g' # append cflags in this gem # end # conf.gem :core => 'mruby-eval' # conf.gem :mgem => 'mruby-onig-regexp' # conf.gem :github => 'mattn/mruby-onig-regexp' # conf.gem :git => 'git@github.com:mattn/mruby-onig-regexp.git', :branch => 'master', :options => '-v'

可以看到,conf.gem既支持路径字符串,也支持:core:mgem:github:git等哈希形式的取源方式,还支持传入代码块对单个 Gem 的编译参数做局部定制。

从 Git 仓库远程取 Gem

conf.gem :git => 'https://github.com/masuidrive/mrbgems-example.git', :branch => 'master' conf.gem :github => 'masuidrive/mrbgems-example', :branch => 'master' conf.gem :bitbucket => 'mruby/mrbgems-example', :branch => 'master'

注意::bitbucket选项只支持 git 协议,当前版本不支持 hg。

如果 Gem 位于仓库的子目录,可用:path指定子目录:

conf.gem github: 'mruby/mruby', path: 'mrbgems/mruby-socket'

本仓库的mruby-bin-mirbmruby-bin-mrbc等 Gem 正是以:core形式被 default.gembox 引入的:

conf.gem :core => "mruby-bin-mrbc"

:core表示从 mruby 自带的mrbgems目录中查找对应名称的 Gem。

从 mgem-list 索引取 Gem

mgem-list 是 mruby 社区的 Gem 索引仓库,通过:mgem选项可以直接引用其中的 Gem:

conf.gem :mgem => 'mruby-yaml' conf.gem :mgem => 'yaml' # 'mruby-' 前缀可以省略

固定依赖的提交版本

需要固定某个 Gem 的检出提交时,使用:checksum_hash选项:

conf.gem mgem: 'mruby-redis', checksum_hash: '3446d19fc4a3f9697b5ddbf2a904f301c42f2f4e'

缺失依赖的自动解析

如果构建中缺少某个 Gem 的依赖,mrbgems 的依赖求解器会从 mruby 核心 Gem(:core)或 mgem-list 中自动引用对应 mrbgem。

同名 Git Gem 冲突与 canonical 选项

当多个基于 git 的 Gem 具有相同的基础名(即默认检出目录名)时,除非它们的仓库 URL、分支名和提交 ID(即 checksum hash)完全一致,否则会报错。可以通过先显式导入自己偏好的版本并设置canonical: true来绕过:

conf.gem github: 'me/mruby-yaml', branch: 'my-hacked-branch', canonical: true

设置后,系统会(在多数情况下静默地)忽略后续针对同名 Gem 的克隆尝试。

需要注意:canonical只影响从 git 克隆 Gem 这一环节,并不解决版本冲突。如果 Gem 的 rakefile 中声明的版本与某个依赖不兼容,构建仍然会失败。

GemBox:批量组合 mrbgem

当需要一次性加入一批 mrbgem,或希望按不同配置切换 Gem 组合时,可以使用 GemBox。GemBox 是一个文件,内部以与config.gem相同的格式列出要加载的 mrbgem,但整体包裹在MRuby::GemBox对象中,通过config.gembox 'boxname'加载。

下面是一个包含mruby-timemrbgems-example的 GemBox 示例:

MRuby::GemBox.new do |conf| conf.gem "#{root}/mrbgems/mruby-time" conf.gem :github => 'masuidrive/mrbgems-example' end

GemBox 与MRuby::Build使用相同的约定。要让 mruby 识别,GemBox 文件必须.gembox扩展名保存在mrbgems目录中。把上面的内容保存为mrbgems/custom.gembox后,在构建配置文件的 build 块中加入:

conf.gembox 'custom'

构建时customGemBox 即被读取,mruby-timemrbgems-example会被加入构建。

如果想把 GemBox 放在 mruby 目录之外,则必须使用绝对路径:

conf.gembox "#{ENV["HOME"]}/mygemboxes/custom"

mruby 自带的两个 GemBox

mruby 内置两个 GemBox:

  • default.gembox:包含 mruby 的若干核心组件。其实现依次引用了stdlibstdlib-extstdlib-iomathmetaprog五个 gembox,并加入mruby-bin-mrbcmruby-bin-mirbmruby-bin-mrubymruby-bin-stripmruby-bin-config五个可执行 Gem;
  • full-core.gembox:包含mrbgems目录下除mruby-bin-debuggermruby-test之外的所有 Gem。其实现直接扫描目录:
MRuby::GemBox.new do |conf| Dir.glob("#{root}/mrbgems/mruby-*/mrbgem.rake") do |x| g = File.basename File.dirname x conf.gem :core => g unless g =~ /^mruby-(?:bin-debugger|test)$/ end end

在 deps/mruby/build_config/default.rb 中正是通过conf.gembox 'default'引入整套默认 Gem 集。此外mrbgems目录下还提供了default-no-fpu.gemboxdefault-no-stdio.gemboxmath.gemboxstdlib.gembox等更细粒度的组合,方便按平台与需求裁剪。

GEM 目录结构

一个最完整的 mrbgem 目录结构如下:

+- GEM_NAME <- Gem 名称 | +- README.md <- Gem 说明文档 | +- mrbgem.rake <- Gem 规格文件(Specification) | +- include/ <- Ruby 扩展所需的头文件(会被导出) | +- mrblib/ <- Ruby 扩展源码 | +- src/ <- C 扩展源码 | +- tools/ <- 可执行程序源码(C) | +- test/ <- 测试代码(Ruby)
  • mrblib目录存放用于扩展 mruby 的纯 Ruby 文件;
  • src目录存放用于扩展 mruby 的 C/C++ 文件;
  • include目录存放 C/C++ 头文件;
  • test目录存放供mrbtest使用的 C/C++ 与纯 Ruby 测试文件;
  • mrbgem.rake是编译 C 与 Ruby 文件所需的规格描述;
  • README.md是 Gem 的简要说明。

以本仓库为例:mruby-time 遵循mrblib + src + mrbgem.rake结构,其 C 实现在 src/time.c 中通过 mruby C API 定义Time类;mruby-bin-mirb 则展示了tools/目录的二进制 Gem 结构。

构建流程与 mrbgem.rake 规格文件

mrbgems 要求每个 Gem 目录下必须存在名为mrbgem.rake的规格文件。一个典型的规格文件如下:

MRuby::Gem::Specification.new('c_and_ruby_extension_example') do |spec| spec.license = 'MIT' spec.author = 'mruby developers' spec.summary = 'Example mrbgem using C and Ruby' end

构建过程会根据该规格编译 Object 文件与 Ruby 文件,编译结果被加入lib/libmruby.a。这个静态库把 Gem 的功能暴露给mrubymirb等工具。

信息属性

MRuby::Gem::Specification内可设置以下信息属性:

  • spec.licensespec.licenses(该 Gem 采用的一个或多个许可证)
  • spec.authorspec.authors(开发者名字或列表)
  • spec.version(当前版本号)
  • spec.description(详细描述)
  • spec.summary(一行短描述,设置后会在 rake 的构建摘要中打印)
  • spec.homepage(主页)
  • spec.requirements(供用户了解的外部需求)

licenseauthor是每个 Gem 必填的属性!

仓库中的真实 Gem 均遵循这一规范,例如 mruby-time/mrbgem.rake:

MRuby::Gem::Specification.new('mruby-time') do |spec| spec.license = 'MIT' spec.author = 'mruby developers' spec.summary = 'standard Time class' end

而 mruby-env/mrbgem.rake 展示了authors(复数)与version的用法:

MRuby::Gem::Specification.new('mruby-env') do |spec| spec.license = 'MIT' spec.authors = 'Internet Initiative Japan Inc.' end

声明依赖:add_dependency

当 Gem 依赖其他 Gem 时,使用spec.add_dependency(gem, *requirements[, default_get_info])

MRuby::Gem::Specification.new('c_and_ruby_extension_example') do |spec| spec.license = 'MIT' spec.author = 'mruby developers' # 添加依赖 mruby-parser,版本必须介于 1.0.0 与 1.5.2 之间 spec.add_dependency('mruby-parser', '>= 1.0.0', '<= 1.5.2') # 使用 GitHub 上任意版本的 mruby-uv spec.add_dependency('mruby-uv', '>= 0.0.0', :github => 'mattn/mruby-uv') # 使用 GitHub 上最新版 mruby-onig-regexp(版本要求可省略) spec.add_dependency('mruby-onig-regexp', :github => 'mattn/mruby-onig-regexp') # 仅测试时激活的额外 gem spec.add_test_dependency('mruby-process', :github => 'iij/mruby-process') end

版本要求与默认 Gem 信息都是可选的。仓库中的实际例子可见 mruby-bin-mrbc/mrbgem.rake:

MRuby::Gem::Specification.new 'mruby-bin-mrbc' do |spec| spec.license = 'MIT' spec.author = 'mruby developers' spec.summary = 'mruby compiler executable' spec.add_dependency 'mruby-compiler', :core => 'mruby-compiler' ... end

以及 mruby-onig-regexp/mrbgem.rake 中的add_dependency 'mruby-string-ext', core: 'mruby-string-ext'

版本运算符

版本要求支持以下运算符:

  • =:等于
  • !=:不等于
  • >:大于
  • <:小于
  • >=:大于等于
  • <=:小于等于
  • ~>:大于等于当前版本,且小于下一个主版本
    • 例 1:'~> 2.2.2'表示>= 2.2.2< 2.3.0
    • 例 2:'~> 2.2'表示>= 2.2.0< 3.0.0

当传入多个版本要求时,依赖必须同时满足全部要求。

默认 Gem 信息与覆盖

可以在add_dependency的最后一个参数传入Hash作为默认 Gem 信息,用于在构建配置中未定义该依赖时使用。其格式与MRuby::Build#gem方法的参数相同,只是不能作为路径型 Gem 位置使用。

当需要某个特定版本的依赖时,在构建配置中使用MRuby::Build#gem即可覆盖默认 Gem。

声明冲突:add_conflict

如果存在互相冲突的 Gem,使用spec.add_conflict(gem, *requirements),其中requirements参数与add_dependency一致:

MRuby::Gem::Specification.new 'some-regexp-binding' do |spec| spec.license = 'BSD' spec.author = 'John Doe' spec.add_conflict 'mruby-onig-regexp', '> 0.0.0' spec.add_conflict 'mruby-hs-regexp' spec.add_conflict 'mruby-pcre-regexp' spec.add_conflict 'mruby-regexp-pcre' end

复杂构建选项

当 Gem 有更复杂的构建需求时,可在规格中使用以下选项:

  • spec.cc.flags(C 编译器参数)
  • spec.cc.defines(C 编译器宏定义)
  • spec.cc.include_paths(C 编译器头文件搜索路径)
  • spec.linker.flags(链接器参数)
  • spec.linker.libraries(链接库)
  • spec.linker.library_paths(链接器附加库路径)
  • spec.bins(生成的可执行文件名)
  • spec.rbfiles(要编译的 Ruby 文件)
  • spec.objs(要编译的 Object 文件)
  • spec.test_rbfiles(集成进 mrbtest 的 Ruby 测试文件)
  • spec.test_objs(集成进 mrbtest 的 Object 测试文件)
  • spec.test_preload(mrbtest 的初始化文件)

此外还可以用spec.mruby.ccspec.mruby.linker为编译器和链接器添加全局参数。

仓库中 mruby-onig-regexp/mrbgem.rake 是复杂构建的典型范例:它先探测系统是否已安装onigmo/oniguruma库(通过search_packagesearch_header_path),找不到时则通过spec.bundle_onigmo方法自动解包捆绑的onigmo-6.2.0.tar.gz、调用 autotools 构建静态库、把产物对象并入libmruby.a,并动态追加cc.definescc.include_paths,完整展示了cclinkerbinsadd_dependency等规格选项的组合用法。

include_paths 与依赖传播

Gem 可以把 include 路径导出给依赖它的其他 Gem。默认情况下,/...绝对路径.../{GEM_NAME}/include会被导出,因此不建议把 Gem 的本地头文件放在include/下。

导出具有追溯性:例如 B 依赖 C、A 依赖 B 时,A 也能获得 C 导出的 include 路径。被导出的 include_paths 会自动追加到 Gem 本地的 include_paths 中;如果构建需求更复杂,可以使用spec.export_include_paths访问器。

C 扩展

mruby 可通过 C API 集成 C 库实现扩展。

前提条件

mrbgems 要求实现名为mrb_YOURGEMNAME_gem_init(mrb_state)的 C 函数,其中YOURGEMNAME替换为 Gem 名称。若 Gem 名为c_extension_example,初始化函数如下:

void mrb_c_extension_example_gem_init(mrb_state* mrb) { struct RClass *class_cextension = mrb_define_module(mrb, "CExtension"); mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, MRB_ARGS_NONE()); }

Finalize 终结函数

mrbgems 还要求实现mrb_YOURGEMNAME_gem_final(mrb_state)终结函数,用于释放资源:

void mrb_c_extension_example_gem_final(mrb_state* mrb) { free(someone); }

从源码结构看,gem_init/gem_final与 mruby 核心的类定义宏(如mrb_define_modulemrb_define_class_method)配合,构成了 C 扩展的标准初始化/清理生命周期。

目录结构示例

+- c_extension_example/ | +- README.md (可选) | +- src/ | | | +- example.c <- C 扩展源码 | +- test/ | | | +- example.rb <- C 扩展测试代码 | +- mrbgem.rake <- Gem 规格文件

以仓库中的 mruby-time 为例,其 src/time.c 通过 mruby C API 定义了完整的Time类(含时间获取、格式化、时区转换等功能),并针对不同平台处理gettimeofdaygmtime_r等可移植性问题,是研究 C 扩展实现细节的优质样本。

Ruby 扩展

mruby 也可以用纯 Ruby 扩展,支持覆写已有类或新增类。把所有 Ruby 文件放入mrblib目录即可。

前提条件

无。

目录结构示例

+- ruby_extension_example/ | +- README.md (可选) | +- mrblib/ | | | +- example.rb <- Ruby 扩展源码 | +- test/ | | | +- example.rb <- Ruby 扩展测试代码 | +- mrbgem.rake <- Gem 规格文件

C 与 Ruby 混合扩展

mruby 可以同时用 C 和 Ruby 扩展:Ruby 文件放入mrblib,C 文件放入src

关键执行顺序mrblib下的 mruby 代码会在 Gem 的 Cinit函数被调用之后执行。因此必须保证mruby 脚本依赖 C 代码,而C 代码不依赖 mruby 脚本

目录结构示例

+- c_and_ruby_extension_example/ | +- README.md (可选) | +- mrblib/ | | | +- example.rb <- Ruby 扩展源码 | +- src/ | | | +- example.c <- C 扩展源码 | +- test/ | | | +- example.rb <- C 与 Ruby 扩展测试代码 | +- mrbgem.rake <- Gem 规格文件

本仓库 H2O 的 mruby 处理程序(lib/handler 下的相关 mruby 集成)正是依托这类混合扩展机制,将 C 层的请求处理能力与 Ruby 层的业务脚本结合起来。

二进制 Gem(Binary Gems)

部分 Gem 会在bin目录下生成可执行文件,这类 Gem 称为二进制 Gem。其命名惯例以mruby-bin为前缀,例如mruby-bin-mirbmruby-bin-strip

mrbgem.rake中通过spec.bins指定可执行文件名。入口函数main()应位于tools/<bin>/*.c中(<bin>为可执行文件名)。<bin>目录下的 C 文件会被编译并链接进可执行文件,但不会包含进libmruby.a;而mrblibsrc下的文件则会进入libmruby.a

强烈建议二进制 Gem 不要包含mrblibsrc目录,以将普通 Gem 与二进制 Gem 明确分离。

目录结构示例

+- mruby-bin-example/ | +- README.md (可选) | +- bintest/ | | | +- example.rb <- 二进制 Gem 测试代码 | +- mrbgem.rake <- Gem 规格文件 | +- mrblib/ <- Ruby 扩展源码(可选) | +- src/ <- C 扩展源码(可选) | +- tools/ | +- example/ <- 可执行文件名称目录 | +- example.c <- 可执行文件源码(含 main)

仓库中的真实范例是 mruby-bin-mrbc/mrbgem.rake:它通过spec.bins声明生成mrbc可执行文件,把tools/mrbc/*.c编译链接为build/bin/mrbc,并将mrbc加入build.bins,同时通过add_dependency 'mruby-compiler', :core => 'mruby-compiler'依赖编译器核心 Gem。类似的还有 mruby-bin-mirb/mrbgem.rake,它额外展示了根据宿主环境探测 readline/linenoise 并动态调整cc.defineslinker.libraries的自动化处理。

在 H2O 中的实际应用

本仓库将 mruby(含 mrbgems 机制)作为嵌入式脚本引擎集成于 H2O Web 服务器中:

  • 构建层面,mruby 通过 build_config.rb 指向 build_config/default.rb,再由conf.gembox 'default'加载 default.gembox;
  • Gem 层面,H2O 在 deps 目录下捆绑了mruby-digestmruby-envmruby-input-streammruby-jsonmruby-onig-regexpmruby-requiremruby-class-new-fiber-safemruby-dirmruby-errnomruby-file-stat等 mrbgem,其mrbgem.rake规格(如 mruby-env/mrbgem.rake、mruby-input-stream/mrbgem.rake)全部遵循本文所述的规范编写;
  • 使用层面,H2O 的 mruby 处理程序(lib/handler/mruby相关源码)通过这些 Gem 获得文件系统、环境变量、正则表达式、JSON、摘要算法等能力,并配合mruby-require实现脚本加载。

如果需要自定义 H2O 内嵌 mruby 的扩展集,可按本文方法编辑构建配置:复制 build_config/default.rb 为自定义配置,用conf.gem/conf.gembox增删 Gem,再通过rake MRUBY_CONFIG=/path/to/myconfig.rb编译,即可得到按需裁剪的 mruby 运行时。

总结

mrbgems 为 mruby 提供了一套完整、标准化的扩展管理体系:conf.gem负责把本地或远程(git/github/bitbucket/mgem)的 Gem 接入构建,MRuby::GemBox支持批量组合与按配置切换,mrbgem.rake规格文件统一描述 Gem 的信息、依赖、冲突与编译选项,gem_init/gem_final规定了 C 扩展的生命周期,mrblib/src/include/tools/test目录划分了各类源码与测试的职责。无论是为 mruby 开发一个简单的Time类扩展,还是像 H2O 一样把整套脚本运行时嵌入服务器,掌握 mrbgems 都是定制与扩展 mruby 的基础。

【免费下载链接】h2oH2O - the optimized HTTP/1, HTTP/2, HTTP/3 server项目地址: https://gitcode.com/gh_mirrors/h2/h2o

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询