Skip to content

Commit e9ccfb5

Browse files
authored
Small improvements for Ruby 4.0 support (#659)
* fix: ensure CString lifetime in memory test Previously, the CString was being created inline and immediately dropped, causing undefined behavior when rb_str_new_cstr tried to read from the dangling pointer. Now we keep the CString alive until after the FFI call. * chore: allow unpredictable_function_pointer_comparisons lint Suppress the new Rust lint warning for function pointer comparisons in generated bindings. * chore: update Nix flake for Ruby 4.0 support - Add nixpkgs-ruby input for better Ruby version management - Configure Ruby 4.0.0-preview2 via .ruby-version file - Refactor devShell to use explicit pkgs prefix - Remove bundler (included with Ruby) - Update flake.lock with new dependencies * chore: add direnv and Ruby version configuration Add .envrc for automatic Nix flake loading and .ruby-version to specify Ruby 4.0.0-preview2. * ci: allow truffleruby failures until 2025-01-01 Add continue-on-error for truffleruby matrix entries due to known compatibility issues. Remove allow_failure after 2025-01-01. * Call ruby_sysinit and ruby_init_stack Update tests to invoke crate::ruby_sysinit (platform-agnostic wrapper for rb_w32_sysinit) and add a stack marker (using std::ptr::addr_of_mut) passed to crate::ruby_init_stack before calling ruby_setup. Ensures proper stdio/VM initialization and correct GC stack scanning, especially on Windows. * Relax Gemfile version constraints and add tsort Unpin several gems in Gemfile (rake, minitest, rake-compiler, rake-compiler-dock, racc, base64, standard) to allow flexible versions. Remove the Ruby-version guard around the standard gem so it's always included. Add tsort as a dependency. * fix: correct ensure block syntax in list_platforms The ensure block was inside the each iterator without a begin block, which is invalid Ruby syntax and causes a parse error with older Ruby parsers. * Fix ternary precedence and small style issues - Fix conditional grouping in cargo_test_task so CI/--verbose check evaluates correctly when deciding test args. - Add parentheses around ternary and comparisons to avoid ambiguity. - Use super(<<~MSG) to pass heredoc safely. - Simplify string interpolation in mkmf and normalize require_relative paths.
1 parent e279ac5 commit e9ccfb5

File tree

15 files changed

+146
-47
lines changed

15 files changed

+146
-47
lines changed

.envrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
use flake

.github/workflows/ci.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,14 @@ jobs:
7070
- os: windows-2025
7171
rust_toolchain: stable
7272
include:
73+
# TODO: Remove allow_failure after 2025-01-01 (known compatibility issues)
7374
- ruby_version: "truffleruby"
75+
allow_failure: true
7476
sys:
7577
os: ubuntu-latest
7678
rust_toolchain: stable
7779
- ruby_version: "truffleruby"
80+
allow_failure: true
7881
sys:
7982
os: macos-15
8083
rust_toolchain: stable
@@ -86,6 +89,12 @@ jobs:
8689
sys:
8790
os: ubuntu-latest
8891
rust_toolchain: stable
92+
# TODO: Remove allow_failure after 2025-01-01 (known Windows compatibility issues)
93+
- ruby_version: "3.4"
94+
allow_failure: true
95+
sys:
96+
os: windows-2025
97+
rust_toolchain: stable
8998
exclude:
9099
- ruby_version: "3.1"
91100
sys:
@@ -99,15 +108,14 @@ jobs:
99108
sys:
100109
os: windows-2022
101110
rust_toolchain: stable
102-
- ruby_version: "3.4"
103-
sys:
104-
os: windows-2025
105-
rust_toolchain: stable
111+
# Ruby 4.0 not available on Windows yet
106112
- ruby_version: "4.0.0-preview2"
107113
sys:
108114
os: windows-2025
109115
rust_toolchain: stable
110116
runs-on: ${{ matrix.sys.os }}
117+
# Allow truffleruby to fail until 2025-01-01 (known compatibility issues)
118+
continue-on-error: ${{ matrix.allow_failure || false }}
111119
steps:
112120
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
113121

.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ruby-4.0.0-preview2

Gemfile

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ source "https://rubygems.org"
55
gemspec path: "gem"
66
gemspec path: "examples/rust_reverse"
77

8-
gem "rake", "~> 13.0"
9-
gem "minitest", "5.15.0"
10-
gem "rake-compiler", "~> 1.3.0" # Small bug in 1.2.4 that breaks Ruby 2.5
11-
gem "rake-compiler-dock", "1.10.0" # This should match the versions used in docker/Dockerfile.*
12-
gem "racc", "~> 1.7"
13-
gem "base64", "~> 0.3.0"
8+
gem "rake"
9+
gem "minitest"
10+
gem "rake-compiler"
11+
gem "rake-compiler-dock"
12+
gem "racc"
13+
gem "base64"
1414
gem "yard"
1515
gem "mutex_m"
16-
17-
if RUBY_VERSION >= "2.7.0"
18-
gem "standard", "~> 1.12.1"
19-
end
16+
gem "standard"
17+
gem "tsort"

Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def cargo_test_task(name, *args, crate: name)
2020
next
2121
end
2222

23-
default_args = ENV["CI"] || extra_args.include?("--verbose") ? [] : ["--quiet"]
24-
test_args = ENV["CI"] || extra_args.include?("--verbose") ? ["--", "--nocapture"] : []
23+
default_args = (ENV["CI"] || extra_args.include?("--verbose")) ? [] : ["--quiet"]
24+
test_args = (ENV["CI"] || extra_args.include?("--verbose")) ? ["--", "--nocapture"] : []
2525
sh "cargo", "test", *default_args, *extra_args, *args, "-p", crate, *test_args
2626
puts "=" * 80
2727
end

crates/rb-sys-tests/src/memory_test.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,19 @@ fn test_rb_gc_guarded_ptr_vec() {
2525
unsafe {
2626
let mut vec_of_values: Vec<VALUE> = Default::default();
2727

28-
let s1 = rb_str_new_cstr(format!("hello world{i}\0").as_ptr() as _);
28+
// Keep the CStrings alive until after rb_str_new_cstr uses them
29+
let cstr1 = std::ffi::CString::new(format!("hello world{i}")).unwrap();
30+
let s1 = rb_str_new_cstr(cstr1.as_ptr());
2931
let s1 = rb_gc_guard!(s1);
3032
vec_of_values.push(s1);
3133

32-
let s2 = rb_str_new_cstr(format!("hello world{i}\0").as_ptr() as _);
34+
let cstr2 = std::ffi::CString::new(format!("hello world{i}")).unwrap();
35+
let s2 = rb_str_new_cstr(cstr2.as_ptr());
3336
let s2 = rb_gc_guard!(s2);
3437
vec_of_values.push(s2);
3538

36-
let s3 = rb_str_new_cstr(format!("hello world{i}\0").as_ptr() as _);
39+
let cstr3 = std::ffi::CString::new(format!("hello world{i}")).unwrap();
40+
let s3 = rb_str_new_cstr(cstr3.as_ptr());
3741
let s3 = rb_gc_guard!(s3);
3842
vec_of_values.push(s3);
3943

crates/rb-sys/src/bindings.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#![allow(rustdoc::invalid_html_tags)]
1515
#![allow(deprecated)]
1616
#![allow(dead_code)]
17+
#![allow(unpredictable_function_pointer_comparisons)]
1718

1819
include!(env!("RB_SYS_BINDINGS_PATH"));
1920

crates/rb-sys/src/utils.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,24 @@ macro_rules! debug_ruby_assert_type {
6464
mod tests {
6565
use super::*;
6666
use rusty_fork::rusty_fork_test;
67+
use std::ptr::addr_of_mut;
6768

6869
rusty_fork_test! {
6970
#[test]
7071
fn test_is_ruby_vm_started() {
7172
assert!(!unsafe { is_ruby_vm_started() });
7273

73-
#[cfg(windows)]
74-
{
75-
let mut argc = 0;
76-
let mut argv: [*mut std::os::raw::c_char; 0] = [];
77-
let mut argv = argv.as_mut_ptr();
78-
unsafe { rb_sys::rb_w32_sysinit(&mut argc, &mut argv) };
79-
}
74+
// Call ruby_sysinit which handles platform-specific initialization
75+
// (rb_w32_sysinit on Windows) and sets up standard file descriptors
76+
let mut argc = 0;
77+
let mut argv: [*mut std::os::raw::c_char; 0] = [];
78+
let mut argv_ptr = argv.as_mut_ptr();
79+
unsafe { crate::ruby_sysinit(&mut argc, &mut argv_ptr) };
80+
81+
// ruby_init_stack must be called before ruby_setup, especially on
82+
// Windows where it's required for proper GC stack scanning
83+
let mut stack_marker: crate::VALUE = 0;
84+
unsafe { crate::ruby_init_stack(addr_of_mut!(stack_marker) as *mut _) };
8085

8186
match unsafe { crate::ruby_setup() } {
8287
0 => {}

flake.lock

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,36 @@
22
description = "Dev environment for rb-sys";
33

44
inputs = {
5-
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
66
rust-overlay.url = "github:oxalica/rust-overlay";
7-
flake-utils.url = "github:numtide/flake-utils";
7+
flake-utils.url = "github:numtide/flake-utils";
8+
nixpkgs-ruby.url = "github:bobvanderlinden/nixpkgs-ruby";
9+
nixpkgs-ruby.inputs.nixpkgs.follows = "nixpkgs";
810
};
911

10-
outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
12+
outputs = { self, nixpkgs, rust-overlay, flake-utils, nixpkgs-ruby, ... }:
1113
flake-utils.lib.eachDefaultSystem (system:
1214
let
1315
overlays = [ (import rust-overlay) ];
1416
pkgs = import nixpkgs {
1517
inherit system overlays;
1618
};
19+
ruby = nixpkgs-ruby.lib.packageFromRubyVersionFile {
20+
file = ./.ruby-version;
21+
inherit system;
22+
};
23+
rustToolchain = pkgs.rust-bin.stable.latest.default;
1724
in
18-
with pkgs;
1925
{
20-
devShells.default = mkShell {
26+
packages.ruby = ruby;
27+
28+
devShells.default = pkgs.mkShell {
2129
buildInputs = [
22-
fastmod
23-
ruby_3_3.devEnv
24-
rust-bin.stable.latest.default
25-
bundler
26-
zsh
27-
nodejs
30+
pkgs.fastmod
31+
ruby
32+
rustToolchain
33+
pkgs.zsh
34+
pkgs.nodejs
2835
];
2936

3037
# Make is so nix develop --impure uses zsh config

0 commit comments

Comments
 (0)