diff --git a/Manifest.txt b/Manifest.txt index 3591f722552e..bb998d7aa4ae 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -193,6 +193,7 @@ bundler/lib/bundler/templates/Gemfile bundler/lib/bundler/templates/gems.rb bundler/lib/bundler/templates/newgem/CHANGELOG.md.tt bundler/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +bundler/lib/bundler/templates/newgem/Cargo.toml.tt bundler/lib/bundler/templates/newgem/Gemfile.tt bundler/lib/bundler/templates/newgem/LICENSE.txt.tt bundler/lib/bundler/templates/newgem/README.md.tt @@ -201,9 +202,11 @@ bundler/lib/bundler/templates/newgem/bin/console.tt 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/extconf.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/main.yml.tt bundler/lib/bundler/templates/newgem/gitignore.tt bundler/lib/bundler/templates/newgem/gitlab-ci.yml.tt diff --git a/bundler/lib/bundler/cli.rb b/bundler/lib/bundler/cli.rb index e1c284130be5..8317bba2d42d 100644 --- a/bundler/lib/bundler/cli.rb +++ b/bundler/lib/bundler/cli.rb @@ -571,6 +571,7 @@ def viz method_option :edit, :type => :string, :aliases => "-e", :required => false, :banner => "EDITOR", :lazy_default => [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" + method_option :rust, :type => :boolean, :default => false, :desc => "Generate the boilerplate for Rust native extension code" method_option :ext, :type => :boolean, :default => false, :desc => "Generate the boilerplate for C extension code" method_option :git, :type => :boolean, :default => true, :desc => "Initialize a git repo inside your library." method_option :mit, :type => :boolean, :desc => "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." diff --git a/bundler/lib/bundler/cli/gem.rb b/bundler/lib/bundler/cli/gem.rb index c4c76d1b69de..c957fc5368a9 100644 --- a/bundler/lib/bundler/cli/gem.rb +++ b/bundler/lib/bundler/cli/gem.rb @@ -65,6 +65,7 @@ def run :email => git_user_email.empty? ? "TODO: Write your email address" : git_user_email, :test => options[:test], :ext => options[:ext], + :rust => options[:rust], :exe => options[:exe], :bundler_version => bundler_dependency_version, :git => use_git, @@ -196,6 +197,15 @@ def run ) end + if options[:rust] + templates.merge!( + "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/Cargo.toml.tt" => "ext/#{name}/Cargo.toml", + "ext/newgem/src/lib.rs.tt" => "ext/#{name}/src/lib.rs", + "Cargo.toml.tt" => "Cargo.toml", + ) + end + if target.exist? && !target.directory? Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] diff --git a/bundler/lib/bundler/templates/newgem/Cargo.toml.tt b/bundler/lib/bundler/templates/newgem/Cargo.toml.tt new file mode 100644 index 000000000000..a7f5c238d49e --- /dev/null +++ b/bundler/lib/bundler/templates/newgem/Cargo.toml.tt @@ -0,0 +1,6 @@ +# This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is +# a Rust project. Your extensions depedencies should be added to the Cargo.toml +# in the ext/ directory. + +[workspace] +members = ["./ext/<%= config[:name] %>"] \ No newline at end of file diff --git a/bundler/lib/bundler/templates/newgem/Gemfile.tt b/bundler/lib/bundler/templates/newgem/Gemfile.tt index de82a63c5ff6..488c76e550e6 100644 --- a/bundler/lib/bundler/templates/newgem/Gemfile.tt +++ b/bundler/lib/bundler/templates/newgem/Gemfile.tt @@ -6,10 +6,14 @@ source "https://rubygems.org" gemspec gem "rake", "~> 13.0" -<%- if config[:ext] -%> +<%- if config[:ext] || config[:rust] -%> gem "rake-compiler" <%- end -%> +<%- if config[:rust] -%> + +gem "rb_sys" +<%- end -%> <%- if config[:test] -%> gem "<%= config[:test] %>", "~> <%= config[:test_framework_version] %>" diff --git a/bundler/lib/bundler/templates/newgem/Rakefile.tt b/bundler/lib/bundler/templates/newgem/Rakefile.tt index b02ada9b6ced..a3548c1a8d9a 100644 --- a/bundler/lib/bundler/templates/newgem/Rakefile.tt +++ b/bundler/lib/bundler/templates/newgem/Rakefile.tt @@ -38,8 +38,9 @@ RuboCop::RakeTask.new require "standard/rake" <% end -%> -<% if config[:ext] -%> -<% default_task_names.unshift(:clobber, :compile) -%> +<% if config[:ext] || config[:rust] -%> +<% default_task_names.unshift(:compile) -%> +<% default_task_names.unshift(:clobber) unless config[:rust] -%> require "rake/extensiontask" task build: :compile diff --git a/bundler/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/bundler/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt new file mode 100644 index 000000000000..2fc4659ed5ee --- /dev/null +++ b/bundler/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt @@ -0,0 +1,17 @@ +[package] +name = <%= config[:name].inspect %> +version = "0.1.0" +edition = "2021" +authors = ["<%= config[:author] %> <<%= config[:email] %>>"] +<%- if config[:mit] -%> +license = "MIT" +<%- end -%> +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +rb-sys = "0.9" +rb-allocator = "0.9" +magnus = { version = "0.3", features = ["rb-sys-interop"] } diff --git a/bundler/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt b/bundler/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt index e918063ddf68..53af1059f6f1 100644 --- a/bundler/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt +++ b/bundler/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt @@ -1,5 +1,11 @@ # frozen_string_literal: true require "mkmf" +<% if config[:rust] -%> +require "rb_sys/mkmf" + +create_rust_makefile(<%= config[:makefile_path].inspect %>) +<% else -%> create_makefile(<%= config[:makefile_path].inspect %>) +<% end -%> diff --git a/bundler/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt b/bundler/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt new file mode 100644 index 000000000000..b0f16b22af72 --- /dev/null +++ b/bundler/lib/bundler/templates/newgem/ext/newgem/src/lib.rs.tt @@ -0,0 +1,20 @@ +use magnus::{define_module, function, prelude::*, Error}; +use rb_allocator::ruby_global_allocator; +use rb_sys::ruby_extension; + +// Ensure that the Ruby C ABI is being used. +ruby_extension!(); + +// Inform Ruby's GC about memory allocations. +ruby_global_allocator!(); + +fn hello(subject: String) -> String { + format!("Hello from Rust, {}!", subject) +} + +#[magnus::init] +fn init() -> Result<(), Error> { + let module = <%= config[:constant_array].map {|c| "define_module(#{c.dump})?"}.join(".") %>; + module.define_singleton_method("hello", function!(hello, 1))?; + Ok(()) +} diff --git a/bundler/lib/bundler/templates/newgem/gitignore.tt b/bundler/lib/bundler/templates/newgem/gitignore.tt index b1c9f9986cc4..8183366ba6e4 100644 --- a/bundler/lib/bundler/templates/newgem/gitignore.tt +++ b/bundler/lib/bundler/templates/newgem/gitignore.tt @@ -6,13 +6,16 @@ /pkg/ /spec/reports/ /tmp/ -<%- if config[:ext] -%> +<%- if config[:ext] || config[:rust] -%> *.bundle *.so *.o *.a mkmf.log <%- end -%> +<%- if config[:rust] -%> +target/ +<%- end -%> <%- if config[:test] == "rspec" -%> # rspec failure tracking diff --git a/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt b/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt index caf6e32f4abf..dc111733b7d2 100644 --- a/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt +++ b/bundler/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -1,7 +1,7 @@ # frozen_string_literal: true require_relative "<%= File.basename(config[:namespaced_path]) %>/version" -<%- if config[:ext] -%> +<%- if config[:ext] || config[:rust] -%> require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" <%- end -%> diff --git a/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt b/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt index ceb2e9b28d30..c6da3781d49c 100644 --- a/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/bundler/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -35,6 +35,9 @@ Gem::Specification.new do |spec| <%- if config[:ext] -%> spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> +<%- if config[:rust] -%> + spec.extensions = ["ext/<%= config[:underscored_name] %>/Cargo.toml"] +<%- end -%> # Uncomment to register a new dependency of your gem # spec.add_dependency "example-gem", "~> 1.0" diff --git a/bundler/spec/commands/newgem_spec.rb b/bundler/spec/commands/newgem_spec.rb index 55a04b69c59f..b7fe7f7abe38 100644 --- a/bundler/spec/commands/newgem_spec.rb +++ b/bundler/spec/commands/newgem_spec.rb @@ -338,6 +338,34 @@ def bundle_exec_standardrb expect(last_command).to be_success end + it "has no rubocop offenses when using --rust and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + bundle "gem #{gem_name} --rust --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no rubocop offenses when using --rust, --test=minitest, and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + bundle "gem #{gem_name} --rust --test=minitest --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no rubocop offenses when using --rust, --test=rspec, and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + bundle "gem #{gem_name} --rust --test=rspec --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + + it "has no rubocop offenses when using --rust, --test=test-unit, and --linter=rubocop flag", :readline do + skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? + bundle "gem #{gem_name} --ext --test=test-unit --linter=rubocop" + bundle_exec_rubocop + expect(last_command).to be_success + end + it "has no standard offenses when using --linter=standard flag", :readline do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? bundle "gem #{gem_name} --linter=standard" @@ -1358,6 +1386,53 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) end end + + context "--rust parameter set" do + let(:flags) { "--rust" } + + before do + bundle ["gem", gem_name, flags].compact.join(" ") + end + + it "builds ext skeleton" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/Cargo.toml")).to exist + expect(bundled_app("#{gem_name}/ext/#{gem_name}/src/lib.rs")).to exist + end + + it "includes rake-compiler" do + expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') + end + + it "includes rb_sys" do + expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rb_sys"') + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include('require "rb_sys/mkmf"') + expect(bundled_app("#{gem_name}/ext/#{gem_name}/extconf.rb").read).to include("create_rust_makefile") + end + + it "includes magnus" do + expect(bundled_app("#{gem_name}/ext/#{gem_name}/Cargo.toml").read).to include("magnus") + end + + it "depends on compile task for build" do + rakefile = strip_whitespace <<-RAKEFILE + # frozen_string_literal: true + + require "bundler/gem_tasks" + require "rake/extensiontask" + + task build: :compile + + Rake::ExtensionTask.new("#{gem_name}") do |ext| + ext.lib_dir = "lib/#{gem_name}" + end + + task default: :compile + RAKEFILE + + expect(bundled_app("#{gem_name}/Rakefile").read).to eq(rakefile) + end + end end context "gem naming with dashed", :readline do diff --git a/bundler/tool/bundler/rubocop23_gems.rb b/bundler/tool/bundler/rubocop23_gems.rb index 0c593d9d6cfa..7a24061e0cc8 100644 --- a/bundler/tool/bundler/rubocop23_gems.rb +++ b/bundler/tool/bundler/rubocop23_gems.rb @@ -10,3 +10,4 @@ gem "rake-compiler" gem "rspec" gem "test-unit" +gem "rb_sys" diff --git a/bundler/tool/bundler/rubocop23_gems.rb.lock b/bundler/tool/bundler/rubocop23_gems.rb.lock index 03aeaae9f293..d9e8d0712e6a 100644 --- a/bundler/tool/bundler/rubocop23_gems.rb.lock +++ b/bundler/tool/bundler/rubocop23_gems.rb.lock @@ -14,6 +14,7 @@ GEM rake (13.0.6) rake-compiler (1.1.7) rake + rb_sys (0.9.19) rexml (3.2.5) rspec (3.10.0) rspec-core (~> 3.10.0) @@ -55,6 +56,7 @@ DEPENDENCIES parallel (= 1.19.2) rake rake-compiler + rb_sys (~> 0.9.19) rspec rubocop (= 0.81.0) test-unit diff --git a/bundler/tool/bundler/rubocop24_gems.rb b/bundler/tool/bundler/rubocop24_gems.rb index 89c6fd31c496..31c1d206602a 100644 --- a/bundler/tool/bundler/rubocop24_gems.rb +++ b/bundler/tool/bundler/rubocop24_gems.rb @@ -11,3 +11,4 @@ gem "rake-compiler" gem "rspec" gem "test-unit" +gem "rb_sys" diff --git a/bundler/tool/bundler/rubocop24_gems.rb.lock b/bundler/tool/bundler/rubocop24_gems.rb.lock index e8436bbcc480..725b1b74f11e 100644 --- a/bundler/tool/bundler/rubocop24_gems.rb.lock +++ b/bundler/tool/bundler/rubocop24_gems.rb.lock @@ -12,6 +12,7 @@ GEM rake (13.0.6) rake-compiler (1.1.7) rake + rb_sys (0.9.19) regexp_parser (2.2.0) rexml (3.2.5) rspec (3.10.0) @@ -57,6 +58,7 @@ DEPENDENCIES parallel (= 1.19.2) rake rake-compiler + rb_sys (~> 0.9.19) rspec rubocop (~> 1.12.1) rubocop-ast (= 1.4.1) diff --git a/bundler/tool/bundler/rubocop_gems.rb b/bundler/tool/bundler/rubocop_gems.rb index 84cb226330a6..9cb740cd151b 100644 --- a/bundler/tool/bundler/rubocop_gems.rb +++ b/bundler/tool/bundler/rubocop_gems.rb @@ -9,3 +9,4 @@ gem "rake-compiler" gem "rspec" gem "test-unit" +gem "rb_sys" diff --git a/bundler/tool/bundler/rubocop_gems.rb.lock b/bundler/tool/bundler/rubocop_gems.rb.lock index 13e541bcfae2..1d4560d6584f 100644 --- a/bundler/tool/bundler/rubocop_gems.rb.lock +++ b/bundler/tool/bundler/rubocop_gems.rb.lock @@ -12,6 +12,7 @@ GEM rake (13.0.6) rake-compiler (1.1.7) rake + rb_sys (0.9.19) regexp_parser (2.2.0) rexml (3.2.5) rspec (3.10.0) @@ -57,6 +58,7 @@ DEPENDENCIES minitest rake rake-compiler + rb_sys (~> 0.9.19) rspec rubocop (~> 1.7) test-unit diff --git a/bundler/tool/bundler/standard23_gems.rb b/bundler/tool/bundler/standard23_gems.rb index eaf26b50985b..3fa61a13f4a8 100644 --- a/bundler/tool/bundler/standard23_gems.rb +++ b/bundler/tool/bundler/standard23_gems.rb @@ -10,3 +10,4 @@ gem "rake-compiler" gem "rspec" gem "test-unit" +gem "rb_sys" diff --git a/bundler/tool/bundler/standard23_gems.rb.lock b/bundler/tool/bundler/standard23_gems.rb.lock index 452fac6eef24..d3ddc58e2c93 100644 --- a/bundler/tool/bundler/standard23_gems.rb.lock +++ b/bundler/tool/bundler/standard23_gems.rb.lock @@ -14,6 +14,7 @@ GEM rake (13.0.6) rake-compiler (1.1.7) rake + rb_sys (0.9.19) rexml (3.2.5) rspec (3.10.0) rspec-core (~> 3.10.0) @@ -60,6 +61,7 @@ DEPENDENCIES parallel (= 1.19.2) rake rake-compiler + rb_sys (~> 0.9.19) rspec standard (= 0.2.5) test-unit diff --git a/bundler/tool/bundler/standard24_gems.rb b/bundler/tool/bundler/standard24_gems.rb index 48bb61b965f5..70158aa9147c 100644 --- a/bundler/tool/bundler/standard24_gems.rb +++ b/bundler/tool/bundler/standard24_gems.rb @@ -11,3 +11,4 @@ gem "rake-compiler" gem "rspec" gem "test-unit" +gem "rb_sys" diff --git a/bundler/tool/bundler/standard24_gems.rb.lock b/bundler/tool/bundler/standard24_gems.rb.lock index 8bc877c141f3..1da7d55f7df1 100644 --- a/bundler/tool/bundler/standard24_gems.rb.lock +++ b/bundler/tool/bundler/standard24_gems.rb.lock @@ -12,6 +12,7 @@ GEM rake (13.0.6) rake-compiler (1.1.7) rake + rb_sys (0.9.19) regexp_parser (2.2.0) rexml (3.2.5) rspec (3.10.0) @@ -63,6 +64,7 @@ DEPENDENCIES parallel (= 1.19.2) rake rake-compiler + rb_sys (~> 0.9.19) rspec rubocop-ast (= 1.4.1) standard (~> 1.0.4) diff --git a/bundler/tool/bundler/standard_gems.rb b/bundler/tool/bundler/standard_gems.rb index 1cd189742df9..20c1ecd82772 100644 --- a/bundler/tool/bundler/standard_gems.rb +++ b/bundler/tool/bundler/standard_gems.rb @@ -9,3 +9,4 @@ gem "rake-compiler" gem "rspec" gem "test-unit" +gem "rb_sys" diff --git a/bundler/tool/bundler/standard_gems.rb.lock b/bundler/tool/bundler/standard_gems.rb.lock index df346ad7ceac..552d8a3aa1b8 100644 --- a/bundler/tool/bundler/standard_gems.rb.lock +++ b/bundler/tool/bundler/standard_gems.rb.lock @@ -12,6 +12,7 @@ GEM rake (13.0.6) rake-compiler (1.1.7) rake + rb_sys (0.9.19) regexp_parser (2.2.0) rexml (3.2.5) rspec (3.10.0) @@ -63,6 +64,7 @@ DEPENDENCIES minitest rake rake-compiler + rb_sys (~> 0.9.19) rspec standard (~> 1.0) test-unit diff --git a/bundler/tool/bundler/test_gems.rb b/bundler/tool/bundler/test_gems.rb index 215d23183e4d..1307a3ac7303 100644 --- a/bundler/tool/bundler/test_gems.rb +++ b/bundler/tool/bundler/test_gems.rb @@ -10,3 +10,4 @@ gem "sinatra", "~> 2.0" gem "rake", "13.0.1" gem "builder", "~> 3.2" +gem "rb_sys" diff --git a/bundler/tool/bundler/test_gems.rb.lock b/bundler/tool/bundler/test_gems.rb.lock index 8e2f0768a617..b051f338f830 100644 --- a/bundler/tool/bundler/test_gems.rb.lock +++ b/bundler/tool/bundler/test_gems.rb.lock @@ -13,6 +13,7 @@ GEM rack-test (1.1.0) rack (>= 1.0, < 3) rake (13.0.1) + rb_sys (0.9.19) ruby2_keywords (0.0.5) sinatra (2.0.8.1) mustermann (~> 1.0) @@ -39,6 +40,7 @@ DEPENDENCIES rack (= 2.0.8) rack-test (~> 1.1) rake (= 13.0.1) + rb_sys (~> 0.9.19) sinatra (~> 2.0) webrick (= 1.7.0)