Ruby 使用 Ruby-build 在 MacOS 上 编译 Portable Ruby

Mark24 · 2024年05月27日 · 最后由 bekean 回复于 2024年06月11日 · 355 次阅读

我的 Blog

大家好,我是 Mark24。

分享下我的笔记,使用 Ruby-build 在 MacOS 上 编译 Portable ruby

设想一下,如果 ruby 可以变成 portable 的,放在 U 盘上就可以带走,传输到任何一台电脑上就可以执行。

Portable Ruby + 你的 Ruby 代码 的 zip 包,就像一个行走的独立软件。就像 Go 打包的一样。

你还可以把他们塞入 一些壳软件里。就像 Electron 那样运行(内部是个浏览器)。

当然 Ruby 社区曾经有很多方案 Traveling Ruby、Ruby Packer,都用各自的方式实现类似的效果,不过都不维护了。

下面用一个简单的方法来制作 Portable Ruby。


截止 2024-05-27 最新版本是 3.3.1。 每个版本因为特性的不同构建是一个动态的过程。就以 3.3.1 为例。

过程偷懒,建立在 ruby-build(https://github.com/rbenv/ruby-build) 的基础上。

不论是 asdf、rvm …… 他们的背后都是 ruby-build 一个方便安装的 standalone 的工具。ruby-build 解决了大部分的问题,我们只需要找到合适的构建参数。

一、前置依赖

1.安装 Mac 的基础工具集

终端输入 xcode-select --install

2.安装上 homebrew

https://brew.sh/

获得 类似于 Linux 上的包管理工具

3.安装 Ruby 编译需要的前置依赖

# 安装前置依赖
# ruby-build 是安装工具
# openssl@3 readline libyaml gmp 是必要的依赖
# rust 是 YJIT 必要的依赖,不装就不会构建 YJIT 功能

brew install ruby-build openssl@3 readline libyaml gmp rust

二、编译

0.知识点

C 语言(CRuby 是 C 语言项目)编译一般分为 3 个基本过程

1)预处理:处理一些前置的宏替换
2)编译:把 .c 代码文件翻译成 .o 机器码文件目标文件
3)链接:把 .o 文件和系统的底层库(比如标准输入输出)正确的关联起来。生成可执行文件

链接这部,有两个基本的实现

1)静态链接
2)动态链接

静态链接比较简单,就是把所有用到的代码打包成一个整体。软件就像一个 exe 文件,带到哪儿都可以执行。
优点就是,随处执行。缺点就是体积大,更新困难,比如你依赖的系统部分有安全缺陷。你必须整体替换。

动态链接,就是软件把用到公共部分(系统、上游 lib)的部分,指他们的动态库(linux 是 so 文件, windows 是 dll 文件,mac 里是 dylib 文件)。
优点:体积小, 如果公共部分有安全漏洞,系统更新,只需要更新动态链接库文件,所有引用的软件都会获得更新。
缺点:除了无法 portable,软件运行的前提是系统拥有相应的 库。

动态链接是常态,不论是 Linux、MacOS、Windows。动态链接的实践这么多年运行的一直很好。通常库都是按照动态链接库方向来设计的。没有提供静态库。

MacOS 还禁止系统动态库进行 静态链接。
  1. 最简单的编译

关键参数:

  • $HOME/portable-ruby 是你存放的目录
  • --enable-load-relative 地址是相对目录,这对我们移动很重要
  • --with-static-linked-ext 静态链接
RUBY_CONFIGURE_OPTS="--enable-load-relative --with-static-linked-ext" ruby-build 3.2.2 $HOME/portable-ruby

2.一些优化选项

可以参考 https://github.com/rbenv/ruby-build

额外的选项

  • --with-out-ext=win32,win32ole 去掉 MacOS 上不需要的拓展
  • --disable-install-doc 关闭文档,减小体积
  • --disable-install-rdoc
  • --disable-dependency-tracking
RUBY_CONFIGURE_OPTS="--enable-load-relative --with-static-linked-ext --with-out-ext=win32,win32ole --disable-install-doc --disable-install-rdoc --disable-dependency-tracking " ruby-build 3.2.2 $HOME/portable-ruby

ruby-build 能做的更多,比如支持交叉编译

三、Portable Ruby

编译正确完成,你应该获得了 portable ruby

在拥有 依赖库的电脑上(对,我们前面解释了,系统部分是禁止 静态链接的)。

你的可以把你的 ruby 代码 + portable ruby 放在一个文件夹里。用 一个 shell 脚本,通过相对路径连接起来执行。

比如这样

#!/usr/bin/env bash
./portable-ruby/bin/ruby ./main.rb

某种意义上,Portable Ruby + Ruby Script 和 Go、Crystal 打包的可执行文件,是一样的。就是大了一点 :D

我的 Blog

Mark24 2024 年我该如何安装 Ruby on Rails ? 提及了此话题。 05月27日 19:48

👍,非常详细,我试着操作一下。 新手想玩 Rails 卡在安装上的第一天。。。 构建出的 Portable Ruby 包含 gem 是吗?用这个 gem install rails 不知道能不能成功。 我在 MacOS 14.5 上用 brew install ruby 成功后,gem install rails 会一直卡在 creating Makefile 这一步,不太明白为什么。

ttys000 回复

emmm…… 是这样

1.Portable Ruby 可能更高级一点

比如你想把 Ruby+Rails+ 你的应用,带着走。用上面的可能合适。

但是没必要,因为你在学习 Rails 不要把自己放在这些奇怪的情境中。

2.如果你只想正常运行 ruby,开发 Rails,而且不想遇到 sudo 问题

我推荐 asdf,这也是我本人开发用的个 asdf 可以管理多个语言、数据库、任何版本相关的。

1)在这里 https://asdf-vm.com/ 安装 asdf

2)添加 ruby

https://github.com/asdf-vm/asdf-ruby

可以参考我之前的博客: https://mark24code.github.io/ruby/2021/12/24/Ruby%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BAasdf+ruby+gem+bundler+%E6%BA%90%E6%9B%BF%E6%8D%A2.html

不过可能有点过时。在安装依赖这部分。Ruby 3.3.1 依赖 openssl@3 这是重点。

# 安装前置依赖
# ruby-build 是安装工具
# openssl@3 readline libyaml gmp 是必要的依赖
# rust 是 YJIT 必要的依赖,不装就不会构建 YJIT 功能

brew install ruby-build openssl@3 readline libyaml gmp rust
Mark24 回复

好的,明白了,谢谢。 我只是为了学习 Rails,使用 asdf 比较合适。

使用 ruby-build 构建需要配置 llvm 相关的环境变量嘛? 我执行命令:

RUBY_CONFIGURE_OPTS="--enable-load-relative --with-static-linked-ext --with-openssl-dir=$(brew --prefix openssl@3)" ruby-build -v 3.3.1 $HOME/portable-ruby

之后,会一直停在这里:

-> make -j 8
Makefile:21362: warning: overriding commands for target `ruby'
Makefile:300: warning: ignoring old commands for target `ruby'
    BASERUBY = /usr/bin/ruby --disable=gems
    CC = clang
    LD = ld
    LDSHARED = clang -dynamiclib
    CFLAGS = -fdeclspec -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef  -fno-common -pipe -arch arm64
    XCFLAGS = -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fno-strict-overflow -fvisibility=hidden -fexcess-precision=standard -DRUBY_EXPORT -I. -I.ext/include/arm64-darwin23 -I./include -I. -I./prism -I./enc/unicode/15.0.0 -I/opt/homebrew/opt/gmp/include
    CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT
    DLDFLAGS = -L/opt/homebrew/opt/gmp/lib -Wl,-undefined,dynamic_lookup -install_name @executable_path/../lib/libruby.3.3.dylib -compatibility_version 3.3 -current_version 3.3.1  -fstack-protector-strong -framework CoreFoundation  -fstack-protector-strong -framework CoreFoundation  -arch arm64
    SOLIBS = -ldl -lobjc -lpthread
    LANG = en_US.UTF-8
    LC_ALL =
    LC_CTYPE = en_US.UTF-8
    MFLAGS = - --jobserver-fds=5,6 -j
    RUSTC = rustc
    YJIT_RUSTC_ARGS = --crate-name=yjit --crate-type=staticlib --edition=2021 -g -C lto=thin -C opt-level=3 -C overflow-checks=on '--out-dir=/var/folders/d5/89cq_k3j2g73crqs3rt_gnfc0000gn/T/ruby-build.20240527230952.4882.rzLkTm/ruby-3.3.1/yjit/target/release/' ./yjit/src/lib.rs
Homebrew clang version 17.0.6
Target: arm64-apple-darwin23.5.0
Thread model: posix
InstalledDir: /opt/homebrew/opt/llvm/bin
ttys000 2024 年我该如何安装 Ruby on Rails ? 提及了此话题。 05月27日 23:22
ttys000 回复

构建有一个过程,需要等待。编译完会自动安装 make install .... 结束就好了

Mark24 回复

好的,那可能是我需要多等一会儿。😅

通过 asdf 安装可以看到具体错误是 unmarshal message: unexpected end of JSON input,详细如下:

asdf install ruby 3.2.4

==> Downloading ruby-3.2.4.tar.gz...
-> curl -q -fL -o ruby-3.2.4.tar.gz https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.4.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 19.6M  100 19.6M    0     0  17.6M      0  0:00:01  0:00:01 --:--:-- 17.6M
==> Installing ruby-3.2.4...
ruby-build: using readline from homebrew
ruby-build: using libyaml from homebrew
ruby-build: using gmp from homebrew
-> ./configure "--prefix=$HOME/.asdf/installs/ruby/3.2.4" --enable-shared --with-readline-dir=/opt/homebrew/opt/readline --with-libyaml-dir=/opt/homebrew/opt/libyaml --with-gmp-dir=/opt/homebrew/opt/gmp --with-ext=openssl,psych,+ --with-openssl-dir=/opt/homebrew/opt/openssl@3
-> make -j 8

BUILD FAILED (macOS 14.5 on arm64 using ruby-build 20240517)

You can inspect the build directory at /var/folders/d5/89cq_k3j2g73crqs3rt_gnfc0000gn/T/ruby-build.20240528102326.67423.7RhdzP
See the full build log at /var/folders/d5/89cq_k3j2g73crqs3rt_gnfc0000gn/T/ruby-build.20240528102326.67423.log

错误日志:

-> make -j 8
Makefile:19118: warning: overriding commands for target `ruby'
Makefile:309: warning: ignoring old commands for target `ruby'
    BASERUBY = /usr/bin/ruby --disable=gems
    CC = clang
    LD = clang
    LDSHARED = clang -dynamiclib
    CFLAGS = -fdeclspec -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wundef   -fno-common -pipe 
    XCFLAGS = -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fno-strict-overflow -fvisibility=hidden -DRUBY_EXPORT -I. -I.ext/include/arm64-darwin23 -I./include -I. -I./enc/unicode/15.0.0 
    CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT   
    DLDFLAGS = -L/opt/homebrew/opt/gmp/lib -Wl,-undefined,dynamic_lookup -install_name /Users/peng/.asdf/installs/ruby/3.2.4/lib/libruby.3.2.dylib -compatibility_version 3.2 -current_version 3.2.4  -fstack-protector-strong -framework CoreFoundation  -fstack-protector-strong -framework CoreFoundation  
    SOLIBS = yjit/target/release/libyjit.a -ldl -lobjc -lpthread 
    LANG = en_US.UTF-8
    LC_ALL = 
    LC_CTYPE = en_US.UTF-8
    MFLAGS = - --jobserver-fds=5,6 -j
    RUSTC = rustc
    YJIT_RUSTC_ARGS = --crate-name=yjit --crate-type=staticlib --edition=2021 -g -C opt-level=3 -C overflow-checks=on '--out-dir=/var/folders/d5/89cq_k3j2g73crqs3rt_gnfc0000gn/T/ruby-build.20240528102326.67423.7RhdzP/ruby-3.2.4/yjit/target/release/' ./yjit/src/lib.rs
2024/05/28 10:24:12 unmarshal message: unexpected end of JSON input
make: *** [main.o] Error 1
make: *** Waiting for unfinished jobs....
Apple clang version 15.0.0 (clang-1500.3.9.4)
Target: arm64-apple-darwin23.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
external command failed with status 2

新手确实应该直接用 asdf 来尝试安装。 直接用 ruby-build 构建的话,stdout 里看不到 external command error,所以就感觉是一直停留在等待 clang 编译那里。用 asdf 可以看到更多错误信息了,可以尝试解决了至少。

ttys000 回复

同感。asdf 确实不错,只是对于开发还 ok。对于只想运行 ruby 的人来说,还是太繁琐。

我还在研究 portable ruby。理想情况:做出了比较完善的,也可以做到直接下载,添加到 path 就可以工作了。免去了本地编译的问题。

这是最近想把 ruby 带着走,产生的想法。

Mark24 回复

是的,如果是对于只想运行 ruby 来说,最好还是有一种办法可以将 app 和 runtime 打包在一起。

asdf 新的替代 mise

我是用的 chruby+ruby-build,使用 ruby-build 安装不同的版本后,chruby 来切换版本

使用工具如 Bundler drift hunters 来管理项目的依赖项,并将这些依赖项打包在一起。这样,即使在不同的环境中,也能确保依赖项的一致性。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号