Skip to content

Commit

Permalink
Add stable API for Ruby 3.4 (#452)
Browse files Browse the repository at this point in the history
* Add stable API for Ruby 3.4

* Update CI to test 3.4

* Fix CI

* Fix tool lookup for mswin

* More robust CC tool detection
  • Loading branch information
ianks authored Nov 21, 2024
1 parent e432f0c commit 9013c95
Show file tree
Hide file tree
Showing 17 changed files with 315 additions and 14 deletions.
20 changes: 13 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
fail-fast: false
matrix:
# Test against all versions supported by rubygems
ruby_version: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
ruby_version: ["2.7", "3.0", "3.1", "3.2", "3.3", "3.4.0-preview2"]
sys:
- os: ubuntu-latest
rust_toolchain: ${{ needs.fetch_ci_data.outputs.minimum-supported-rust-version }}
Expand All @@ -53,6 +53,14 @@ jobs:
sys:
os: macos-latest
rust_toolchain: stable
- ruby_version: "ruby-debug"
sys:
os: ubuntu-24.04
rust_toolchain: stable
- ruby_version: mswin

Check warning on line 60 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / 🧪 Test (mswin, windows-2022, stable-x86_64-pc-windows-msvc)

mswin builds use ruby-master, and which is unstable and may break your build at any time (see https://github.com/MSP-Greg/ruby-loco/issues/12)
sys:
os: windows-2022
rust_toolchain: stable-x86_64-pc-windows-msvc
exclude:
# Missing symbols for some reason, need to fix
- ruby_version: "2.6"
Expand All @@ -67,12 +75,10 @@ jobs:
sys:
os: macos-latest
rust_toolchain: stable
# MSC version mismatch, need to fix
# include:
# - ruby_version: mswin
# sys:
# os: windows-2022
# rust_toolchain: stable-x86_64-pc-windows-msvc
- ruby_version: "3.4.0-preview2"
sys:
os: windows-2022
rust_toolchain: stable
runs-on: ${{ matrix.sys.os }}
steps:
- uses: actions/checkout@v4
Expand Down
22 changes: 21 additions & 1 deletion crates/rb-sys-build/src/cc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,27 @@ fn get_tool(env_var: &str, default: &str) -> Command {
let mut tool_args = shellsplit(tool_args).into_iter();
let tool = tool_args.next().unwrap_or_else(|| default.to_string());

let mut cmd = if Path::new(&tool).is_file() {
fn tool_exists(tool_name: &str) -> std::io::Result<bool> {
let path = PathBuf::from(tool_name);

if path.is_file() {
return Ok(true);
}

match Command::new(tool_name).spawn() {
Ok(_) => Ok(true),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(false)
} else {
Err(e)
}
}
}
}

let mut cmd = if tool_exists(&tool).unwrap_or(false) {
debug_log!("[INFO] using {tool} for {env_var}");
new_command(&tool)
} else {
debug_log!("[WARN] {tool} tool not found, falling back to {default}");
Expand Down
5 changes: 4 additions & 1 deletion crates/rb-sys-test-helpers/src/ruby_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,10 @@ mod tests {
assert_eq!("RuntimeError", exception.classname());
assert_eq!("oh no", exception.message().unwrap());
#[cfg(ruby_gt_2_4)]
assert!(exception.full_message().unwrap().contains("eval:1:in `"),);
{
let message = exception.full_message().unwrap();
assert!(message.contains("eval:1:in "), "message: {}", message);
}
})
}
}
8 changes: 6 additions & 2 deletions crates/rb-sys-test-helpers/src/ruby_test_executor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::error::Error;
use std::panic;
use std::ptr::addr_of_mut;
use std::sync::mpsc::{self, SyncSender};
use std::sync::Once;
use std::thread::{self, JoinHandle};
Expand All @@ -10,7 +11,7 @@ use crate::once_cell::OnceCell;
use rb_sys::rb_ext_ractor_safe;
use rb_sys::{
rb_errinfo, rb_inspect, rb_protect, rb_set_errinfo, rb_string_value_cstr, ruby_exec_node,
ruby_process_options, ruby_setup, Qnil, VALUE,
ruby_init_stack, ruby_process_options, ruby_setup, Qnil, VALUE,
};

static mut GLOBAL_EXECUTOR: OnceCell<RubyTestExecutor> = OnceCell::new();
Expand Down Expand Up @@ -141,6 +142,9 @@ pub unsafe fn setup_ruby_unguarded() {
rb_sys::rb_w32_sysinit(&mut argc, &mut argv);
}

let mut stack_marker: VALUE = 0;
ruby_init_stack(addr_of_mut!(stack_marker) as *mut _);

match ruby_setup() {
0 => {}
code => panic!("Failed to setup Ruby (error code: {})", code),
Expand All @@ -150,7 +154,7 @@ pub unsafe fn setup_ruby_unguarded() {
let mut argv: [*mut i8; 3] = [
"ruby\0".as_ptr() as _,
"-e\0".as_ptr() as _,
"nil\0".as_ptr() as _,
"\0".as_ptr() as _,
];

ruby_process_options(argv.len() as _, argv.as_mut_ptr() as _) as _
Expand Down
2 changes: 2 additions & 0 deletions crates/rb-sys-tests/src/stable_api_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ macro_rules! parity_test {
use rb_sys::stable_api;
let data = $data_factory;

assert_ne!(stable_api::get_default().version(), (0, 0));

#[allow(unused)]
let rust_result = unsafe { stable_api::get_default().$func(data) };
#[allow(unused_unsafe)]
Expand Down
8 changes: 6 additions & 2 deletions crates/rb-sys/build/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::{
};
use version::Version;

const SUPPORTED_RUBY_VERSIONS: [Version; 9] = [
const SUPPORTED_RUBY_VERSIONS: [Version; 10] = [
Version::new(2, 3),
Version::new(2, 4),
Version::new(2, 5),
Expand All @@ -23,6 +23,7 @@ const SUPPORTED_RUBY_VERSIONS: [Version; 9] = [
Version::new(3, 1),
Version::new(3, 2),
Version::new(3, 3),
Version::new(3, 4),
];

fn main() {
Expand Down Expand Up @@ -55,7 +56,10 @@ fn main() {
export_cargo_cfg(&mut rbconfig, &mut cfg_capture_file);

#[cfg(feature = "stable-api")]
stable_api_config::setup(&rbconfig).expect("could not setup stable API");
if let Err(e) = stable_api_config::setup(&rbconfig) {
eprintln!("Failed to setup stable API: {}", e);
std::process::exit(1);
}

if is_link_ruby_enabled() {
link_libruby(&mut rbconfig);
Expand Down
2 changes: 1 addition & 1 deletion crates/rb-sys/build/version.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::RbConfig;

#[allow(dead_code)]
pub const LATEST_STABLE_VERSION: Version = Version::new(3, 3);
pub const LATEST_STABLE_VERSION: Version = Version::new(3, 4);
#[allow(dead_code)]
pub const MIN_SUPPORTED_STABLE_VERSION: Version = Version::new(2, 6);

Expand Down
4 changes: 4 additions & 0 deletions crates/rb-sys/src/special_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! Makes it easier to reference important Ruby constants, without having to dig
//! around in bindgen's output.
use std::ffi::c_long;

use crate::{ruby_special_consts, VALUE};

pub const Qfalse: ruby_special_consts = ruby_special_consts::RUBY_Qfalse;
Expand All @@ -15,6 +17,8 @@ pub const Qnil: ruby_special_consts = ruby_special_consts::RUBY_Qnil;
pub const Qundef: ruby_special_consts = ruby_special_consts::RUBY_Qundef;
pub const IMMEDIATE_MASK: ruby_special_consts = ruby_special_consts::RUBY_IMMEDIATE_MASK;
pub const FIXNUM_FLAG: ruby_special_consts = ruby_special_consts::RUBY_FIXNUM_FLAG;
pub const FIXNUM_MIN: c_long = c_long::MIN / 2;
pub const FIXNUM_MAX: c_long = c_long::MAX / 2;
pub const FLONUM_MASK: ruby_special_consts = ruby_special_consts::RUBY_FLONUM_MASK;
pub const FLONUM_FLAG: ruby_special_consts = ruby_special_consts::RUBY_FLONUM_FLAG;
pub const SYMBOL_FLAG: ruby_special_consts = ruby_special_consts::RUBY_SYMBOL_FLAG;
Expand Down
17 changes: 17 additions & 0 deletions crates/rb-sys/src/stable_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ use crate::VALUE;
use std::os::raw::{c_char, c_long};

pub trait StableApiDefinition {
const VERSION_MAJOR: u32;
const VERSION_MINOR: u32;

fn version(&self) -> (u32, u32) {
(Self::VERSION_MAJOR, Self::VERSION_MINOR)
}

/// Get the length of a Ruby string (akin to `RSTRING_LEN`).
///
/// # Safety
Expand Down Expand Up @@ -139,10 +146,20 @@ use compiled as api;
#[cfg_attr(ruby_eq_3_1, path = "stable_api/ruby_3_1.rs")]
#[cfg_attr(ruby_eq_3_2, path = "stable_api/ruby_3_2.rs")]
#[cfg_attr(ruby_eq_3_3, path = "stable_api/ruby_3_3.rs")]
#[cfg_attr(ruby_eq_3_4, path = "stable_api/ruby_3_4.rs")]
mod rust;
#[cfg(not(stable_api_export_compiled_as_api))]
use rust as api;

impl std::fmt::Debug for api::Definition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StableApiDefinition")
.field("VERSION_MAJOR", &api::Definition::VERSION_MAJOR)
.field("VERSION_MINOR", &api::Definition::VERSION_MINOR)
.finish()
}
}

/// Get the default stable API definition for the current Ruby version.
pub const fn get_default() -> &'static api::Definition {
const API: api::Definition = api::Definition {};
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/compiled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ extern "C" {
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 0;
const VERSION_MINOR: u32 = 0;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> std::os::raw::c_long {
impl_rstring_len(obj)
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/ruby_2_6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ compile_error!("This file should only be included in Ruby 2.6 builds");
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 2;
const VERSION_MINOR: u32 = 6;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
assert!(self.type_p(obj, crate::ruby_value_type::RUBY_T_STRING));
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/ruby_2_7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ compile_error!("This file should only be included in Ruby 2.7 builds");
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 2;
const VERSION_MINOR: u32 = 7;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
assert!(self.type_p(obj, crate::ruby_value_type::RUBY_T_STRING));
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/ruby_3_0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ compile_error!("This file should only be included in Ruby 3.0 builds");
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 3;
const VERSION_MINOR: u32 = 0;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
unsafe {
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/ruby_3_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ compile_error!("This file should only be included in Ruby 3.1 builds");
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 3;
const VERSION_MINOR: u32 = 1;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
assert!(self.type_p(obj, crate::ruby_value_type::RUBY_T_STRING));
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/ruby_3_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ compile_error!("This file should only be included in Ruby 3.2 builds");
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 3;
const VERSION_MINOR: u32 = 2;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
assert!(self.type_p(obj, crate::ruby_value_type::RUBY_T_STRING));
Expand Down
3 changes: 3 additions & 0 deletions crates/rb-sys/src/stable_api/ruby_3_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ compile_error!("This file should only be included in Ruby 3.3 builds");
pub struct Definition;

impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 3;
const VERSION_MINOR: u32 = 3;

#[inline]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
assert!(self.type_p(obj, crate::ruby_value_type::RUBY_T_STRING));
Expand Down
Loading

0 comments on commit 9013c95

Please sign in to comment.