Ruby打包技术之旅

结论:

似乎找到了 2 个 Portable Ruby 实例

  • [Windows Ruby (Portable) 3.3.1.1 ](https://community.chocolatey.org/packages/ruby.portable)
  • [MacOS homebrew/portable-ruby ](https://github.com/Homebrew/homebrew-portable-ruby/pkgs/container/portable-ruby%2Fportable-ruby)

原文:

背景

大家好,我是 Mark24。

设想一下,如果你在用 Ruby 开发一个 GUI 应用,或者是 游戏。如何把产物可以送到你用户的手中。尽可能的轻松跑起来?

我目前感兴趣的是游戏应用。所以后面都是建立在游戏跑在终端的角度考虑。

虽然我们在讨论 Ruby ,但是对于所有动态脚本语言的思路是通用的。

解决打包动态语言的问题。最后一公里,如何送到用户手中。

思路一: 编译并静态链接,经典二进制包

1. 像静态语言一样,获得直接的二进制文件 ❌

比如 Go、Rust、Crystal 的构建产物。

结论:

Go、Rust、Crystal …… 他们依然是在有限条件下运行。只不过这种条件实际上特别宽泛,好像他们的产物可以在各种系统下运行。

实际上 MacOS、Linux、Windows 的底层都是不鼓励静态链接。并且一些关键的包,也不提供静态链接需要的库。

这是为了体积考虑,也是为了安全更新考虑。

这些能够相对来说把自己打成静态链接的语言,实际上都做了大量的工作,自己实现了底层需要的部分。

动态语言无法直接把代码打包成这样。 这条路是违背原理的。

2:极致的静态方向 ✅

这个思路是 MRuby。

MRuby 是一个轻量级的 Ruby 为嵌入式设计。它可以交叉编译成不同的架构。被设计的尽可能的少依赖,多拓展。

一定程度上,MRuby 就像是 Go。

可以用 MRuby 来构建应用、游戏。 MRuby 也有 SDL 的绑定

2.1 Dragon Ruby ✅

这里有篇演讲, Dragon Ruby 的游戏引擎设计者,如何使用 MRuby 来构建一个应用。

Dragon Ruby 从 IDE 到 游戏产物全部是静态二进制。

但是具体的原理不详。依然不知道 Dragon Ruby 是如何做到的。

2.2 Taylor ✅

Taylor 是个个人开源框架,试图挑战 Dragon Ruby。

Taylor 的思路也是经典思路,容器中构建一个可以被静态的环境,绕过系统(MacOS 不允许静态链接系统 lib)。

这些代码可能很难理解,在于他们究竟如何在发挥具体作用。

Taylor 正在重大重构中,但是目前的版本,是完全可以工作的!

思路二:解释器+项目代码 => 压缩包

这个思路需要一个 可以移动执行的 Ruby 解释器。

1. 静态编译 ruby 为 portable ruby ✅

如果拥有了 Portable Ruby,那么 软件包 = (Portable Ruby + 项目代码)。

这条路相对可行。

还是前面的问题,Ruby 没有像 Go 等实现了全部的底层依赖的静态库。所以 编译 != portable。

Portable 的重点就是,尽可能的不依赖。如果实在无法避开的依赖,比如 Linux 中的 glibc(系统底层),需要使用较低版本来编译。 因为 glibc 永远是高版本兼容低版本,所以这样尽可能的获得兼容性。

Crystal、Go …… 他们一样。也只能工作在有限的 glibc 中。

Crystal 给出了平台很好体现了这一点:Crystal Platform Support

用户不需要安装 Ruby,但是需要安装 Ruby 需要的底层库。来获得动态链接库。

这个思路获得了成功。

  • 1)让你本地安装 lib;或者直接安装 ruby(过程中就获得了需要的 lib)
  • 2)打包 portable ruby
  • 3)使用 Mac 的 App 壳应用

  • App 壳应用 Gosu

可以获得一个 Mac 的应用。

1.1 Portable rub + Portable libs 🤔 ✅

前面说了,如果可以创造出 静态的包。Ruby 也可以像 Go、Java 一样。这里参考这样一个项目,尝试在容器中模拟一个这样的环境。尽可能把所需的依赖全部集成起来,产出 portable ruby

不过这个产物我没怎么跑起来。但是这个是经典思路,是完全可行的。

app code + (Portable Ruby + lib) = software

思路三: 普通思路,前置安装器 ✅

用户安装 Ruby 运行游戏。由于前面无法实现彻底的静态打包,即使是安装依赖库,整个过程是差不多的。用户依然要安装。

如果这样避不开。推荐常见的处理办法 —— 前置的安装器(Installer)。解决环境依赖问题。

在 Windows 上 Ruby 是需要 安装包来安装。整个过程就像这样。

这一点,在 Windows 上也成功实现了:

  • Ruby2D 的 demo
  • Raylib-bindings 的 demo

构建过程和 Sample Project: ruby-windows-example

思路四: 切换可以打包的语言

1. 使用 静态语言 Crystal ✅ 🕘

Crystal 的语法和 Ruby 非常相似,也有 游戏库、GUI 的绑定。

可以做到类似的事情。这一点就像 C++

但是缺点是 Crystal 目前还在建设中。

Crystal 对 MacOS ARM、Windows 的支持还不足。

现在无法当作一个成熟方案。

2. 使用 JRuby(Java) ✅

Java 其实采用了类似的思路,自己实现了底层。所以 Java 自身可以打包成静态的二进制。

我们可以把打包工作建立在 Java 的基础上。

这个实践方向是 Glimmer

Glimmer DSL for SWT 能够在 JRuby 之上将 Ruby 应用程序打包到原生安装程序(如 Mac DMG/PKG/APP、Windows MSI/EXE 和 Linux RPM/DEB)中,使开发者能够给最终用户(非程序员)一个单一的文件来运行,以安装所有需要的内容,比如 JRuby(可以运行任何 Ruby 代码)、它的 JVM 依赖项,以及正在安装的应用程序:

Glimmer DSL for LibUI,它直接在 Ruby 上运行而不是 JRuby,也有一个关于打包 Ruby 应用程序的部分,你可能想要查看(它提到了 Windows 和 Mac 的打包解决方案):

以下是使用 Glimmer DSL for SWT 打包的应用程序示例,这些应用程序由最终用户安装,没有问题:

这些都是作者发来的例子。尝试跑了几个,没有成功。 还需要研究研究。

总结

如何把 Ruby 带到终端,其实一直不停的有人研究。项目生生死死。这里列举一些,供参考。

1)容器打包, 静态链接 portable ruby 思路:

2)临时文件系统思路:

3)JRuby 思路:

4)Portable Ruby 思路:

5)只打包应用脚本,指定系统 Ruby

  • platypus 只打包你的脚本,封装成 app,只适合简单脚本

6)静态语言

使用 Crystal , Ruby 语法的 Go like 语言开发应用

7)使用 Zig

这是一个问号,Zig 作为一个新语言可以作为 C 的环境,而且自己实现了所有的静态库。

不知道 Zig 作为 CRuby 的编译器会如何? 但是 Zig 目前依然在发展中。

8)使用容器

容器技术是任何语言的一个打包工具。

对于开发者友好,但是终端用户还是有门槛的。

不适合游戏应用。

9)Gem

如果都能接受用户总归要自己安装 Ruby 的设定。

把游戏、应用,封装成 gem,可以自动处理依赖、版本问题。

10)切换 Ruby 的实现: CRuby 无法实现静态打包

  • artichoke Rust 实现的 Ruby 。开在开发中。(暂不支持 gem)
  • natalie C++实现的 Ruby。开发中。可以 编译 纯 Ruby 脚本。(暂不支持 gem)

补充:

  • [Windows Ruby (Portable) 3.3.1.1 ](https://community.chocolatey.org/packages/ruby.portable)
  • [MacOS homebrew/portable-ruby ](https://github.com/Homebrew/homebrew-portable-ruby/pkgs/container/portable-ruby%2Fportable-ruby)

补充资料:

Mark24

Everything can Mix.