-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add cargo builder for rust extensions #5175
Conversation
Thanks for opening a pull request and helping make RubyGems and Bundler better! Someone from the RubyGems team will take a look at your pull request shortly and leave any feedback. Please make sure that your pull request has tests for any changes or added functionality. We use GitHub Actions to test and make sure your change works functionally and uses acceptable conventions, you can review the current progress of GitHub Actions in the PR status window below. If you have any questions or concerns that you wish to ask, feel free to leave a comment in this PR or join our #rubygems or #bundler channel on Slack. For more information about contributing to the RubyGems project feel free to review our CONTRIBUTING guide |
Though: It would be nice if this thing “just worked” with rake-compiler. That would enable cross-compilation and what not. I think in order to do that, it might be best to do the whole cargo compilation logic extconf.rn. Although I’m not sure if that would work… |
Linking two rust staticlibs together will cause symbol conflicts because each contains a copy of the standard library. If you are linking only a single rust staticlib into a dylib you should probably ensure that all mangled symbols and all symbols used by rust itself are not exported from the dylib (rustc normally handles this for you when compiling cdylibs). Using two rust cdylibs in the same program on the other hand is fine. |
So do you think it's best to raise an error when staticlib is used @bjorn3? |
I think that would be the best option indeed. I presume it would be easier to forbid staticlibs for now and maybe allow them in the future with appropriate safety guards than to allow them now and possibly have to backtrack if it causes issues. |
Another option might be to add
Thoughts? |
That is unfortunately not enough. The standard library doesn't get recompiled when specifying |
This recommendation makes sense when only a single rust library is linked. Unfortunately it breaks down as soon as multiple rust libraries are linked. Another argument against staticlibs is that they don't have an easy way to specify the required dynamic libraries to link against. To get this list you need to pass |
Thank you so much for this info! Going to raise unless we detect a cdylib. |
@bjorn3 Added a patch to raise unless a dylib is found Also added full integration spec, and things seem to be working well. Hopefully we can get some more eyes on this 👀 |
This is on my radar for whole time. I'm just waiting for proper time to start reviewing this. Few initial ideas, questions. Are there any gems we can check using this already? (edit: I see there is some list at https://bugs.ruby-lang.org/issues/15759, I'll check on the approach in there.) Would it be possible to integrate this into Can we make Rust extension testing optional (enabled only when Rust tools are around) so our test suite doesn't rely on Rust being set on the dev. machine? |
I'm so glad to see this being (hopefully) a part of rubygems. I spent enormous amount of time building it manually here. In short: I followed a very similar path. Rust library (actually, its C bindings, but it doesn't matter) is compiled statically and included with Compiling Rust lib to dynamic library seems fine, but actually I don't really understand the reason behind it. What is the conflict of standard libraries here? There's a single crate that (once compiled) includes a single version of Rust stdlib, and dylibs are allowed to share code and symbols. IIRC it is even possible to But actually maybe I'm wrong and maybe it's easier to build it this way: Rust dylib doesn't know about Ruby dependencies and it's 100% valid on its own, it's isolated and self-contained. Ruby dylib that wraps it is also linked with Quick question (sorry if it doesn't belong to this PR directly): is there a way to customize generated Makefile? Or will it run Also, what about targets? I think it's irrelevant for linux/mac, but on windows |
Update: following in the footsteps of pgx, I've setup a macro for rb-sys named #[macro_use]
extern crate rb_sys;
ruby_extension!()!;
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Init_my_gem() {
// ...
} This will set everything up needed to load the extension at runtime. |
I think some of the jobs just timed out @simi, could you trigger a re-run? |
done 🙏 |
Let me please check the code once more time. I think there is some chance to DRY some stuff shared across builders. |
Leaving this here as a note if anyone wants to give cross-compilation a shot after this is merged... I think the mapping to target triples looks something like this: MAPPING = Hash[
"x86-mingw32" => "i686-pc-windows-gnu",
"x64-mingw-ucrt" => "x86_64-pc-windows-gnu",
"x64-mingw32" => "x86_64-pc-windows-gnu",
"x86-linux" => "i686-unknown-linux-gnu",
"x86_64-linux" => "x86_64-unknown-linux-gnu",
"arm-linux" => "arm-unknown-linux-gnueabihf",
"aarch64-linux" => "aarch64-unknown-linux-gnu",
"x86_64-darwin" => "x86_64-apple-darwin",
"arm64-darwin" => "aarch64-apple-darwin",
].freeze |
@simi I hope to merge this before Ruby 3.2.0-preview1 release. Can we refactor/improve builders after merging this? |
Great job everyone. I'll open additional PR with |
Btw. I would consider this still experimental feature for now. |
@simi Thanks! |
Awesome!! Thanks for the help everyone ❤️ Now onto making cross-compilation with rake-compiler as breezy as possible, so rubygems users don’t have to have a cargo tool chain installed! |
Add cargo builder for rust extensions (cherry picked from commit fe96fb6)
Add cargo builder for rust extensions (cherry picked from commit fe96fb6)
how about dsl
|
@pynixwang Looks like the #[rubyclass(module = "Wasmer")]
pub struct Baz {
inner: i32,
};
#[rubymethods]
impl Foo {
pub fn bar(&self, baz: &Baz) -> RubyResult<…> {
let inner = &baz.inner;
…
}
} While |
After a couple years of stagnation, and thanks to the help from @qubitrenegade and @briankung finally got around to building a Rust gem extension builder for Rubygems.
How it works
tl;dr here is a fully working example of a Rust gem, and an excellent blog post from @briankung
Cargo.toml
file is detected, the newGem::Ext::CargoBuilder
is invoked.cargo rustc
and handle the linking, and all of the gymnastics around thatmy_rust_extension.{so,bundle,dll}
)Why the extconf?
My reason is this: In order for ruby to successfully load a ruby extension (viadlopen
), its first check to see thatruby_xmalloc
matches on the built library. This ensures that the extension is only loaded if it was compiled with the same version of Ruby.We no longer need to do this since we are correctly handling the linking process using Cargo.
Hopefully this is on the right track, would love to get some feedback and get this merged at some point!
closes #4903