编译指南

译者 Lanza Schneider

mruby 使用 Rake 来进行编译。

一、准备工作

你需要准备下列工具:

  • 支持 C99 标准的 C 语言编译器套件,这包括链接器等 (如 gcc、clang)
  • Parser 生成器 (bison)
  • Ruby 至少 2.0 版本 (ruby 或 jruby 等都可)

注意,MacOS 自带的 bison 工具对编译 mruby 来说已经太老了,你需要用 brew install bison 并把它添加到 $PATH,当然可以的话,也顺便把 ruby 更新一下吧。

非必须的工具:

  • git (便于更新 mruby 源代码,以及获取 mrbgems)
  • C++ 编译器套件 (用于支持含有 C++ 代码的 mrbgem)

二、编译

如果只是要用默认配置来编译 mruby,那只要在 mruby 源代码目录下使用 rake 命令即可。如果要生成和运行测试工具,请用 rake test;清除构建文件请用 rake clean。要看所有的构建选项,请用 rake -v

上面说的是默认配置,如果你想用其它的配置文件来构建 mruby,可以用 MRUBY_CONFIG 环境变量来指定一个配置文件 (也可以用 CONFIG),如果没有找到配置文件,就会转而使用 build_config/${MRUBY_CONFIG}.rb 作为配置文件。顺带一提,默认配置文件是 build_config/default.rb

显然配置文件是一个 Ruby 源代码文件。它包含了用于构建 mruby 的配置对象。

三、构建配置

  • 如果你在什么新的平台上构建 mruby 成功,请你把你的构建配置文件 pull-request 到 mruby 的 repo。

工具链

GCC

要设置编译工具链为GCC,只需在构建中编写 conf.toolchain :gcc 即可

clang

和GCC差不多,写 conf.toolchain :clang 就行

Visual Studio 2010/2012/2013

conf.toolchain :visualcpp

Android

conf.toolchain :android

构建 Android 平台的 mruby,你需要 Android NDK,以及 NDK 的 toolchain path 环境变量:ANDROID_STANDALONE_TOOLCHAIN

可执行程序

你可以设置需要构建出的mruby可执行程序,如:

  • mruby(mruby解释器程序)
  • mirb(mruby的REPL,交互式解释器)

这部分的设置在mrbgems部分就完成了,请参见mrbgems部分。

文件目录分隔符

如果你在什么特殊的平台构建,可以自己指定文件目录分隔符,比如:

conf.file_separator = '/'

C语言编译器

conf.cc do |cc|
  cc.command = 'gcc'        # 编译器命令
  cc.flags = ['-D_WIN32']   # 编译器的flags
  cc.include_paths = []     # 这里填入编译过程中的文件搜索目录
  cc.defines = []           # 这里是预定义
  cc.compile_options = []   # 这里是一些编译选项
end

链接器

conf.linker do |linker|
  linker.command = ...        # 链接器命令
  linker.flags = ...          # 链接器flags
  linker.libraries = ...      # 附加库名称
  linker.library_paths = ...  # 附加库搜索目录
  linker.link_options = ...   # 链接选项
end

归档器(Archiver, 就是把若干对象文件合并成静态库的工具)

conf.archiver do |archiver|
  archiver.command = ...          # 归档器命令
  archiver.archive_options = ...  # 归档选项
end

Parser生成器

conf.yacc do |yacc|
  yacc.command = ...          # yacc命令
  yacc.compile_options = ...  # 编译选项
end

文件扩展名

conf.exts do |exts|
  exts.object = ...     # 目标文件扩展名 (一般是 .o)
  exts.executable = ... # 可执行文件扩展名
  exts.library = ...    # 静态库文件扩展名 (一般是 .a)
end

预分配符号(Presym)

从 mruby3.0 起,一些 ruby symbol 将会被预先记录成 presym,从而节省运行时的 RAM 开销……当然,如果出于一些原因,你想关闭这个功能的话,可以使用 conf.disable_presym

在构建过程当中,mrbc 工具在交叉编译的环境下都会在该配置下被编译。

Mrbgems (mruby扩展包)

mruby 可以通过 mrbgem 的方式进行扩展,要启用一个指定的 gem,你可以使用 conf.gem

启用一个已经集成在源码目录下的gem:

conf.gem :core => 'mruby-something'

启用一个在github上托管的gem:

conf.gem :github => 'someone/mruby-another'

启用一个binary gem(往往会产出一个可执行文件):

conf.gem :core => 'mruby-bin-mruby'

启用一个GemBox(若干Gems组成的集合):

conf.gembox "default"

GemBox就是一组Gems组成的集合配置文件,参见 mrbgems/default.gembox

如果你安装了cruby,你可以安装一个叫做mgem的RubyGem,它可以提供给你一个已经注册过的mrbgems列表。

Mrbtest

可以用 conf.build_mrbtest_lib_only 使得构建过程只产出 mrbtest.a

Bintest

用cruby测试mrbgems所产出的可执行文件

这部分内容可以看看mrbgems目录下的 mruby-bin-*/bintest/*.rb

conf.enable_bintest

C++ ABI

一般情况下,mruby 使用 sjlj(setjmp/longjmp) 来实现异常处理。不过在 C++ 环境下,这个做法就没有办法正常地释放栈对象了,为了支持使用 C++ 编写的 mrbgems,mruby 可以使用 C++ 的异常处理来避免这个情况。

关于这方面的配置,有两个等级可以选择。conf.enable_cxx_exception 可以启用 C++ 异常来代替 sjlj,不过仍使用 C ABI。另一个就是 conf.enable_cxx_abi,如果启用它,那么所有文件都会用C++编译器来进行编译。

禁用 C++ 异常

如果你的编译器不支持 C++,你也确定不会使用一些由 C++ 编写成的 mrbgems,那就可以禁用 C++ 异常,使用 conf.disable_cxx_exception 就可以了

如果你这么做了,但还是启用了包含 C++ 的 mrbgems,那么就会收到报错

调试模式

conf.enable_debug 可以打开调试

调试模式开启时:

  • MRB_DEBUG 会被添加到编译器宏定义,意味着 mrb_assert (...) 也会启用
  • mrbc 生成的代码将会包含调试信息,这样就可以获得更清晰的 backtrace 信息

交叉编译(Cross-Compilation)

mruby 支持从当前平台交叉编译到另一个平台。要准备交叉编译,你的配置文件中需要有一个交叉编译的配置对象 (MRuby::CrossBuild 的实例),比如:

MRuby::CrossBuild.new ('32bit') do |conf|
  conf.toolchain :gcc
  conf.cc.flags = ['-m32']
  conf.linker.flags = ['-m32']
end

任何用在 MRuby::Build 中的配置选项都可以用于 CrossBuild 当中,参见 build_config 目录来获取示例

四、构建过程

构建前,mruby 源码根目录将会创建一个 build 目录,整个构建过程中产生的文件都会位于该目录下。它的结构看起来像这样:

+- build
***|
***+- host
*******|
*******+- bin <- 可执行程序 (mirb mrbc mruby)
*******|
*******+- lib <- 静态库 (libmruby.a libmruby_core.a)
*******|
*******+- mrblib
*******|
*******+- src
*******|
*******+- test <- mrbtest 工具
*******|
*******+- tools
**********|
**********+- mirb
**********|
**********+- mrbc
**********|
**********+- mruby

编译工作流如下:

编译所有 src 目录下的源代码 (编译产物存放在 build/host/src)

生成 parser 语法文件 (生成结果存放在 build/host/src/y.tab.c)

编译上一步的产物 y.tab.c

将这时所有的编译产物归档为 libmruby_core.a,存放在 build/host/lib 下

编译 tools/mrbc/mrbc.c,并和上面的静态库链接成 mrbc 工具存放在 build/host/bin 下

用上一步生成的 mrbc 工具编译 mrblib 下所有的 *.rb 文件,生成为 build/host/mrblib/mrblib.c

编译上一步的产物 mrblib.c

将这时所有的编译产物归档为 libmruby.a,存放在 build/host/lib 下

将 mrbgems/mruby-bin-mruby/tools/mruby/mruby.c 编译,并和上面的静态库链接成 mruby 工具存放在 build/host/bin 下

和上面类似,产出 mirb 程序

交叉编译的情况

假设我们建立了一个叫作 i386 的交叉编译配置,那么 build 目录看起来像这样:

+- build
***|
***+- host
*******|
*******+- bin <- 本地平台的 可执行程序 (mirb mrbc mruby)
*******|
*******+- lib <- 本地平台的 静态库 (libmruby.a libmruby_core.a)
*******|
*******+- mrblib
*******|
*******+- src
*******|
*******+- test <- 本地平台的 mrbtest 工具
*******|
*******+- tools
**********|
**********+- mirb
**********|
**********+- mrbc
**********|
**********+- mruby
***+- i386
*******|
*******+- bin <- i386 平台的 可执行程序 (mirb mrbc mruby)
*******|
*******+- lib <- i386 平台的 静态库 (libmruby.a libmruby_core.a)
*******|
*******+- mrblib
*******|
*******+- src
*******|
*******+- test <- i386 平台的 mrbtest 工具
*******|
*******+- tools
**********|
**********+- mirb
**********|
**********+- mrbc
**********|
**********+- mruby

可以看到,除了 host 外,又多出了为交叉编译而创建的目录。

交叉编译的工作流一开始和普通编译并无不同,在本地平台的构建完成后,交叉编译的流程如下:

交叉编译所有 src 目录下的源代码 (编译产物存放在 build/i386/src)

生成 parser 语法文件 (生成结果存放在 build/i386/src/y.tab.c)

交叉编译上一步的产物 y.tab.c

用 mrbc 工具编译 mrblib 下所有的 *.rb 文件,生成为 build/i386/mrblib/mrblib.c

交叉编译上一步的产物 mrblib.c

将这时所有的编译产物归档为 libmruby.a,存放在 build/i386/lib 下

将 mrbgems/mruby-bin-mruby/tools/mruby/mruby.c 编译,并和上面的静态库链接成 mruby 工具存放在 build/i386/bin 下

和上面类似,产出 mirb 程序

将这时的 C 源码编译产物归档为 libmruby_core.a,存放在 build/i386/lib 下

交叉编译 tools/mrbc/mrbc.c,并和上面的静态库链接成 mrbc 工具存放在 build/i386/bin 下

五、构建配置示例

最小构建

要构建一个最小配置的 mruby,你可以建立一个 CrossBuild,编写如下:

MRuby::CrossBuild.new ('Minimal') do |conf|
  toolchain :gcc
  conf.cc.defines = % w (MRB_NO_STDIO)
  conf.bins = []
end

这个构建禁用所有 stdio 相关的功能,且不会产出可执行程序

六、将mruby嵌入您的应用当中

构建过程完成后,你就可以获得一个 libmruby.a,你可以将它链接到你的应用程序当中。

你可以用 mruby-config 获取链接它所需要的配置清单。