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

Mark24 · 2024年05月27日 · 最后由 sinos 回复于 2024年06月09日 · 1111 次阅读

我的 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 来切换版本

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