Skip to content

Commit

Permalink
Add stable-api support for Ruby 3.3 (#290)
Browse files Browse the repository at this point in the history
* Add `stable-api` support for Ruby 3.3

* Fix opaqueify for type aliased structs

* Enable windows tests

* Stop testing against Ruby 2.4
  • Loading branch information
ianks authored Dec 27, 2023
1 parent f6a5000 commit 6935bd4
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 13 deletions.
7 changes: 6 additions & 1 deletion .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.4", "2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "head"]
ruby_version: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "head"]
sys:
- os: ubuntu-latest
rust_toolchain: ${{ needs.fetch_ci_data.outputs.minimum-supported-rust-version }}
Expand All @@ -49,6 +49,11 @@ jobs:
sys:
os: windows-2022
rust_toolchain: stable-x86_64-pc-windows-msvc
exclude:
- ruby_version: 3.3 # remove this once Ruby 3.3 binaries are available for windows
sys:
os: windows-2022
rust_toolchain: stable
runs-on: ${{ matrix.sys.os }}
steps:
- uses: actions/checkout@v4
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,29 @@ jobs:
run: rake
- name: "matsadler/magnus"
slug: magnus-0.5
ref: "0.5.4"
ref: "0.5.5"
run: cargo test
- name: "matsadler/magnus"
slug: magnus-head
ref: "05359fcd2369f014570f1e417c7c2d462625af5e"
run: cargo test
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
rust: ["stable"]
ruby: ["2.7", "3.2", "ruby-head"]
# Should re-include these after unstable-api is enabled
ruby: ["2.7", "3.2", "3.3", "ruby-head"]
exclude:
- os: windows-latest
ruby: ruby-head
ruby: '3.3' # remove this once 3.3 binaries are available for windows
repo:
name: "matsadler/magnus"
slug: magnus-head
ref: "05359fcd2369f014570f1e417c7c2d462625af5e"
run: cargo test
- os: windows-latest
ruby: ruby-head
ruby: '3.3' # remove this once 3.3 binaries are available for windows
repo:
name: "matsadler/magnus"
slug: magnus-0.5
ref: "0.5.4"
ref: "0.5.5"
run: cargo test

runs-on: ${{ matrix.os }}
Expand Down
4 changes: 3 additions & 1 deletion crates/rb-sys-build/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ pub fn generate(
.blocklist_item("^_bindgen_ty_9.*")
};

let bindings = stable_api::opaqueify_bindings(bindings, &mut wrapper_h);
let bindings = stable_api::opaqueify_bindings(rbconfig, bindings, &mut wrapper_h);

let mut tokens = {
write!(std::io::stderr(), "{}", wrapper_h)?;
let bindings = bindings.header_contents("wrapper.h", &wrapper_h);
let code_string = bindings.generate()?.to_string();
syn::parse_file(&code_string)?
Expand Down Expand Up @@ -141,6 +142,7 @@ fn default_bindgen(clang_args: Vec<String>) -> bindgen::Builder {
.blocklist_item("^rb_native.*")
.opaque_type("^__sFILE$")
.merge_extern_blocks(true)
.generate_comments(false)
.size_t_is_usize(env::var("CARGO_FEATURE_BINDGEN_SIZE_T_IS_USIZE").is_ok())
.impl_debug(cfg!(feature = "bindgen-impl-debug"))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
Expand Down
75 changes: 71 additions & 4 deletions crates/rb-sys-build/src/bindings/stable_api.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
use std::vec;

use quote::ToTokens;

use crate::RbConfig;

const OPAQUE_STRUCTS: [&str; 2] = ["RString", "RArray"];

const OPAQUE_STRUCTS_RUBY_3_3: [&str; 3] = [
"rb_matchext_struct",
"rb_internal_thread_event_data",
"rb_io_internal_buffer",
];

/// Generate opaque structs for the given bindings.
pub fn opaqueify_bindings(bindings: bindgen::Builder, wrapper_h: &mut String) -> bindgen::Builder {
OPAQUE_STRUCTS.iter().fold(bindings, |bindings, name| {
pub fn opaqueify_bindings(
rbconfig: &RbConfig,
bindings: bindgen::Builder,
wrapper_h: &mut String,
) -> bindgen::Builder {
let version_specific_opaque_structs =
get_version_specific_opaque_structs(rbconfig.major_minor());
let structs_to_opaque = OPAQUE_STRUCTS
.iter()
.chain(&version_specific_opaque_structs);

structs_to_opaque.fold(bindings, |bindings, name| {
gen_opaque_struct(bindings, name, wrapper_h)
})
}
Expand All @@ -25,6 +45,16 @@ pub fn categorize_bindings(syntax: &mut syn::File) {
s.ident = syn::Ident::new(&new_name, s.ident.span());
opaque_idents_to_swap.push(new_name);

opaque_items.push(item.clone());
} else {
normal_items.push(item.clone());
}
} else if let syn::Item::Type(t) = item {
if t.ident.to_string().contains("rb_sys__Opaque__") {
let new_name = t.ident.to_string().replace("rb_sys__Opaque__", "");
t.ident = syn::Ident::new(&new_name, t.ident.span());
opaque_idents_to_swap.push(new_name);

opaque_items.push(item.clone());
} else {
normal_items.push(item.clone());
Expand All @@ -35,6 +65,7 @@ pub fn categorize_bindings(syntax: &mut syn::File) {
let body = &mut f.block;
let code = body.clone().to_token_stream().to_string();
let new_code = code.replace("rb_sys__Opaque__", "super::stable::");
let new_code = syn::parse_str::<syn::Block>(&new_code).unwrap();

*body = syn::parse_quote! {
{
Expand All @@ -50,22 +81,47 @@ pub fn categorize_bindings(syntax: &mut syn::File) {
}
}

for item in normal_items.iter_mut() {
if let syn::Item::Type(ref mut t) = item {
if let Ok(syn::Type::Path(ref mut type_path)) =
syn::parse2::<syn::Type>(t.ty.to_token_stream())
{
if opaque_idents_to_swap.contains(&type_path.path.segments[0].ident.to_string()) {
let new_ident = syn::Ident::new(
&type_path.path.segments[0].ident.to_string(),
type_path.path.segments[0].ident.span(),
);
t.ty = syn::parse_quote! { crate::internal::#new_ident };
}
}
}
}

for mut item in normal_items {
if let syn::Item::Struct(s) = &mut item {
if opaque_idents_to_swap.contains(&s.ident.to_string()) {
internal_items.push(s.clone());
internal_items.push(syn::Item::Struct(s.clone()));
s.attrs.push(syn::parse_quote! {
#[deprecated(note = "To improve API stability with ruby-head, direct usage of Ruby internal structs has been deprecated. To migrate, please replace the usage of this internal struct with its counterpart in the `rb_sys::stable` module. For example, instead of `use rb_sys::rb_sys__Opaque__ExampleStruct;`, use `use rb_sys::stable::ExampleStruct;`. If you need to access the internals of these items, you can use the provided `rb-sys::macros` instead.")]
});
unstable_items.push(item);
} else {
excluded_items.push(item);
}
} else if let syn::Item::Type(t) = &mut item {
if opaque_idents_to_swap.contains(&t.ident.to_string()) {
internal_items.push(syn::Item::Type(t.clone()));
t.attrs.push(syn::parse_quote! {
#[deprecated(note = "To improve API stability with ruby-head, direct usage of Ruby internal structs has been deprecated. To migrate, please replace the usage of this internal struct with its counterpart in the `rb_sys::stable` module. For example, instead of `use rb_sys::rb_sys__Opaque__ExampleStruct;`, use `use rb_sys::stable::ExampleStruct;`. If you need to access the internals of these items, you can use the provided `rb-sys::macros` instead.")]
});
unstable_items.push(item);
} else {
excluded_items.push(item);
}
} else {
excluded_items.push(item);
}
}

*syntax = syn::parse_quote! {
/// Contains all items that are not yet categorized by ABI stability.
/// These items are candidates for promotion to `stable` or `unstable`
Expand Down Expand Up @@ -117,3 +173,14 @@ fn gen_opaque_struct(
.opaque_type(&struct_name)
.allowlist_type(struct_name)
}

fn get_version_specific_opaque_structs(major_minor: (u32, u32)) -> Vec<&'static str> {
let mut result = vec![];
let (major, minor) = major_minor;

if major == 3 && minor >= 3 {
result.extend(OPAQUE_STRUCTS_RUBY_3_3)
}

result
}
1 change: 1 addition & 0 deletions crates/rb-sys-test-helpers/src/once_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ impl<T> OnceCell<T> {
}
});

#[allow(useless_ptr_null_checks)]
while !self.ready.load(Ordering::Acquire) && !self.value_ptr.get().is_null() {
std::thread::yield_now();
}
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, 2);
pub const LATEST_STABLE_VERSION: Version = Version::new(3, 3);
#[allow(dead_code)]
pub const MIN_SUPPORTED_STABLE_VERSION: Version = Version::new(2, 6);

Expand Down
1 change: 1 addition & 0 deletions crates/rb-sys/src/stable_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ use compiled as api;
#[cfg_attr(ruby_eq_3_0, path = "stable_api/ruby_3_0.rs")]
#[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")]
mod rust;
#[cfg(not(stable_api_export_compiled_as_api))]
use rust as api;
Expand Down
4 changes: 4 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 @@ -161,10 +161,12 @@ impl StableApiDefinition for Definition {
}
}

#[inline]
unsafe fn symbol_p(&self, obj: VALUE) -> bool {
self.static_sym_p(obj) || self.dynamic_sym_p(obj)
}

#[inline]
unsafe fn float_type_p(&self, obj: VALUE) -> bool {
if self.flonum_p(obj) {
true
Expand All @@ -175,6 +177,7 @@ impl StableApiDefinition for Definition {
}
}

#[inline]
unsafe fn rb_type(&self, obj: VALUE) -> crate::ruby_value_type {
use crate::ruby_special_consts::*;
use crate::ruby_value_type::*;
Expand All @@ -199,6 +202,7 @@ impl StableApiDefinition for Definition {
}
}

#[inline]
unsafe fn dynamic_sym_p(&self, obj: VALUE) -> bool {
if self.special_const_p(obj) {
false
Expand Down
Loading

0 comments on commit 6935bd4

Please sign in to comment.