Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,13 @@ bundler/lib/bundler/templates/newgem/bin/setup.tt
bundler/lib/bundler/templates/newgem/circleci/config.yml.tt
bundler/lib/bundler/templates/newgem/exe/newgem.tt
bundler/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt
bundler/lib/bundler/templates/newgem/ext/newgem/build.rs.tt
bundler/lib/bundler/templates/newgem/ext/newgem/extconf-c.rb.tt
bundler/lib/bundler/templates/newgem/ext/newgem/extconf-rust.rb.tt
bundler/lib/bundler/templates/newgem/ext/newgem/newgem.c.tt
bundler/lib/bundler/templates/newgem/ext/newgem/newgem.h.tt
bundler/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt
bundler/lib/bundler/templates/newgem/github/workflows/build-gems.yml.tt
bundler/lib/bundler/templates/newgem/github/workflows/main.yml.tt
bundler/lib/bundler/templates/newgem/gitignore.tt
bundler/lib/bundler/templates/newgem/gitlab-ci.yml.tt
Expand Down
12 changes: 4 additions & 8 deletions bundler/lib/bundler/cli/gem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def initialize(options, gem_name, thor)
@extension = options[:ext]

validate_ext_name if @extension
validate_rust_builder_rubygems_version if @extension == "rust"
end

def run
Expand Down Expand Up @@ -136,6 +135,9 @@ def run
case config[:ci]
when "github"
templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml")
if extension == "rust"
templates.merge!("github/workflows/build-gems.yml.tt" => ".github/workflows/build-gems.yml")
end
config[:ci_config_path] = ".github "
when "gitlab"
templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml")
Expand Down Expand Up @@ -208,6 +210,7 @@ def run
templates.merge!(
"Cargo.toml.tt" => "Cargo.toml",
"ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml",
"ext/newgem/build.rs.tt" => "ext/#{name}/build.rs",
"ext/newgem/extconf-rust.rb.tt" => "ext/#{name}/extconf.rb",
"ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs",
)
Expand Down Expand Up @@ -456,12 +459,5 @@ def rubocop_version
def standard_version
"1.3"
end

def validate_rust_builder_rubygems_version
if Gem::Version.new(rust_builder_required_rubygems_version) > Gem.rubygems_version
Bundler.ui.error "Your RubyGems version (#{Gem.rubygems_version}) is too old to build Rust extension. Please update your RubyGems using `gem update --system` or any other way and try again."
exit 1
end
end
end
end
6 changes: 6 additions & 0 deletions bundler/lib/bundler/templates/newgem/Cargo.toml.tt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
[workspace]
members = ["./ext/<%= config[:name] %>"]
resolver = "2"

[profile.release]
# By default, debug symbols are stripped from the final binary which makes it
# harder to debug if something goes wrong. It's recommended to keep debug
# symbols in the release build so that you can debug the final binary if needed.
debug = true
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ publish = false
crate-type = ["cdylib"]

[dependencies]
magnus = { version = "0.6.2" }
magnus = { version = "0.7.1" }
rb-sys = { version = "0.9", features = ["stable-api-compiled-fallback"] }

[build-dependencies]
rb-sys-env = "0.2.2"

[dev-dependencies]
rb-sys-test-helpers = { version = "0.2.2" }
5 changes: 5 additions & 0 deletions bundler/lib/bundler/templates/newgem/ext/newgem/build.rs.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = rb_sys_env::activate()?;

Ok(())
}
15 changes: 13 additions & 2 deletions bundler/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use magnus::{function, prelude::*, Error, Ruby};

fn hello(subject: String) -> String {
format!("Hello from Rust, {subject}!")
pub fn hello(subject: String) -> String {
format!("Hello {subject}, from Rust!")
}

#[magnus::init]
Expand All @@ -10,3 +10,14 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
module.define_singleton_method("hello", function!(hello, 1))?;
Ok(())
}

#[cfg(test)]
mod tests {
use rb_sys_test_helpers::ruby_test;
use super::hello;

#[ruby_test]
fn test_hello() {
assert_eq!("Hello world, from Rust!", hello("world".to_string()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: Build gems

on:
push:
tags:
- "v*"
- "cross-gem/*"
workflow_dispatch:

jobs:
ci-data:
runs-on: ubuntu-latest
outputs:
result: ${{ steps.fetch.outputs.result }}
steps:
- uses: oxidize-rb/actions/fetch-ci-data@v1
id: fetch
with:
supported-ruby-platforms: |
exclude: ["arm-linux", "x64-mingw32"]
stable-ruby-versions: |
exclude: ["head"]

source-gem:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Build gem
run: bundle exec rake build

- uses: actions/upload-artifact@v3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with:
name: source-gem
path: pkg/*.gem

cross-gem:
name: Compile native gem for ${{ matrix.platform }}
runs-on: ubuntu-latest
needs: ci-data
strategy:
matrix:
platform: ${{ fromJSON(needs.ci-data.outputs.result).supported-ruby-platforms }}
steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- uses: oxidize-rb/actions/cross-gem@v1
id: cross-gem
with:
platform: ${{ matrix.platform }}
ruby-versions: ${{ join(fromJSON(needs.ci-data.outputs.result).stable-ruby-versions, ',') }}

- uses: actions/upload-artifact@v3
with:
name: cross-gem
path: ${{ steps.cross-gem.outputs.gem-path }}
9 changes: 8 additions & 1 deletion bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

require_relative "<%= File.basename(config[:namespaced_path]) %>/version"
<%- if config[:ext] -%>
require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>"
# Attempt to load a versioned extension based on the Ruby version.
# Fall back to loading the non-versioned extension if version-specific loading fails.
begin
RUBY_VERSION =~ /(\d+\.\d+)/
require "#{Regexp.last_match(1)}/<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>"
rescue LoadError
require "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>"
end
<%- end -%>

<%- config[:constant_array].each_with_index do |c, i| -%>
Expand Down
10 changes: 3 additions & 7 deletions bundler/lib/bundler/templates/newgem/newgem.gemspec.tt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ Gem::Specification.new do |spec|
spec.license = "MIT"
<%- end -%>
spec.required_ruby_version = ">= <%= config[:required_ruby_version] %>"
<%- if config[:ext] == 'rust' -%>
spec.required_rubygems_version = ">= <%= config[:rust_builder_required_rubygems_version] %>"
<%- end -%>

spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"

spec.metadata["homepage_uri"] = spec.homepage
Expand All @@ -40,12 +36,12 @@ Gem::Specification.new do |spec|
<%- if config[:ext] == 'c' || config[:ext] == 'rust' -%>
spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"]
<%- end -%>
<%- if config[:ext] == 'rust' -%>
spec.add_dependency "rb_sys", "~> 0.9"
<%- end -%>

# Uncomment to register a new dependency of your gem
# spec.add_dependency "example-gem", "~> 1.0"
<%- if config[:ext] == 'rust' -%>
spec.add_dependency "rb_sys", "~> 0.9.91"
<%- end -%>

# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
Expand Down
8 changes: 8 additions & 0 deletions bundler/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ RSpec.describe <%= config[:constant_name] %> do
expect(<%= config[:constant_name] %>::VERSION).not_to be nil
end

<%- if config[:ext] == 'rust' -%>
it "can call into Rust" do
result = <%= config[:constant_name] %>.hello("world")

expect(result).to be("Hello earth, from Rust!")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs eq as these will be different objects I think

     Failure/Error: expect(result).to be("Hello earth, from Rust!")
     
       expected #<String:1640> => "Hello earth, from Rust!"
            got #<String:1648> => "Hello earth, from Rust!"
     
       Compared using equal?, which compares object identity,
       but expected and actual are not the same object. Use
       `expect(actual).to eq(expected)` if you don't care about
       object identity in this example.
Suggested change
expect(result).to be("Hello earth, from Rust!")
expect(result).to eq("Hello earth, from Rust!")
Suggested change
expect(result).to be("Hello earth, from Rust!")
expect(result).to eq("Hello world, from Rust!")

end
<%- else -%>
it "does something useful" do
expect(false).to eq(true)
end
<%- end -%>
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ class <%= config[:minitest_constant_name] %> < Minitest::Test
refute_nil ::<%= config[:constant_name] %>::VERSION
end

<%- if config[:ext] == 'rust' -%>
def test_hello_world
assert_equal "Hello earth, from Rust!", <%= config[:constant_name] %>.hello("world")
end
<%- else -%>
def test_it_does_something_useful
assert false
end
<%- end -%>
end
41 changes: 26 additions & 15 deletions bundler/spec/commands/newgem_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1560,18 +1560,6 @@ def create_temporary_dir(dir)
end
end

context "--ext parameter set with rust and old RubyGems" do
it "fails in friendly way" do
if ::Gem::Version.new("3.3.11") <= ::Gem.rubygems_version
skip "RubyGems compatible with Rust builder"
end

expect do
bundle ["gem", gem_name, "--ext=rust"].compact.join(" ")
end.to raise_error(RuntimeError, /too old to build Rust extension/)
end
end

context "--ext parameter set with rust" do
let(:flags) { "--ext=rust" }

Expand All @@ -1590,12 +1578,11 @@ def create_temporary_dir(dir)
expect(bundled_app("#{gem_name}/ext/#{gem_name}/Cargo.toml")).to exist
expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist
expect(bundled_app("#{gem_name}/ext/#{gem_name}/src/lib.rs")).to exist
expect(bundled_app("#{gem_name}/ext/#{gem_name}/build.rs")).to exist
end

it "includes rake-compiler, rb_sys gems and required_rubygems_version constraint" do
it "includes rake-compiler constraint" do
expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"')
expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "rb_sys"')
expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.required_rubygems_version = ">= ')
end

it "depends on compile task for build" do
Expand All @@ -1618,6 +1605,30 @@ def create_temporary_dir(dir)

expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile)
end

it "configures the crate such that `cargo test` works", :ruby_repo, :mri_only do
env = setup_rust_env
gem_path = bundled_app(gem_name)
result = sys_exec("cargo test", env: env, dir: gem_path)

expect(result).to include("1 passed")
end

def setup_rust_env
skip "rust toolchain of mingw is broken" if RUBY_PLATFORM.match?("mingw")

env = {
"CARGO_HOME" => ENV.fetch("CARGO_HOME", File.join(ENV["HOME"], ".cargo")),
"RUSTUP_HOME" => ENV.fetch("RUSTUP_HOME", File.join(ENV["HOME"], ".rustup")),
"RUSTUP_TOOLCHAIN" => ENV.fetch("RUSTUP_TOOLCHAIN", "stable"),
}

system(env, "cargo", "-V", out: IO::NULL, err: [:child, :out])
skip "cargo not present" unless $?.success?
# Hermetic Cargo setup
RbConfig::CONFIG.each {|k, v| env["RBCONFIG_#{k}"] = v }
env
end
end
end

Expand Down
1 change: 1 addition & 0 deletions bundler/spec/support/filters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def inspect
config.filter_run_excluding jruby_only: RUBY_ENGINE != "jruby"
config.filter_run_excluding truffleruby_only: RUBY_ENGINE != "truffleruby"
config.filter_run_excluding man: Gem.win_platform?
config.filter_run_excluding mri_only: RUBY_ENGINE != "ruby"

config.filter_run_when_matching :focus unless ENV["CI"]
end
Loading