summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/bundler/plugin/api/source.rb6
-rw-r--r--lib/bundler/rubygems_ext.rb17
-rw-r--r--lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb2
-rw-r--r--lib/rubygems/ext.rb1
-rw-r--r--lib/rubygems/ext/builder.rb3
-rw-r--r--lib/rubygems/ext/cargo_builder.rb305
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder.rb148
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/.gitignore1
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock374
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml10
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb21
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec10
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/custom_name/src/lib.rs30
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/.gitignore1
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock374
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml10
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb21
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/rust_ruby_example.gemspec8
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/src/lib.rs42
19 files changed, 1372 insertions, 12 deletions
diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb
index 32b1d0ee38..a6ae08237c 100644
--- a/lib/bundler/plugin/api/source.rb
+++ b/lib/bundler/plugin/api/source.rb
@@ -309,12 +309,6 @@ module Bundler
end
# @private
- # Returns true
- def bundler_plugin_api_source?
- true
- end
-
- # @private
# This API on source might not be stable, and for now we expect plugins
# to download all specs in `#specs`, so we implement the method for
# compatibility purposes and leave it undocumented (and don't support)
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index cc06b9ee93..78422ceb76 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -4,6 +4,17 @@ require "pathname"
require "rubygems/specification"
+# We can't let `Gem::Source` be autoloaded in the `Gem::Specification#source`
+# redefinition below, so we need to load it upfront. The reason is that if
+# Bundler monkeypatches are loaded before RubyGems activates an executable (for
+# example, through `ruby -rbundler -S irb`), gem activation might end up calling
+# the redefined `Gem::Specification#source` and triggering the `Gem::Source`
+# autoload. That would result in requiring "rubygems/source" inside another
+# require, which would trigger a monitor error and cause the `autoload` to
+# eventually fail. A better solution is probably to completely avoid autoloading
+# `Gem::Source` from the redefined `Gem::Specification#source`.
+require "rubygems/source"
+
require_relative "match_platform"
module Gem
@@ -22,11 +33,7 @@ module Gem
alias_method :rg_loaded_from, :loaded_from
def full_gem_path
- # this cannot check source.is_a?(Bundler::Plugin::API::Source)
- # because that _could_ trip the autoload, and if there are unresolved
- # gems at that time, this method could be called inside another require,
- # thus raising with that constant being undefined. Better to check a method
- if source.respond_to?(:path) || (source.respond_to?(:bundler_plugin_api_source?) && source.bundler_plugin_api_source?)
+ if source.respond_to?(:root)
Pathname.new(loaded_from).dirname.expand_path(source.root).to_s.tap{|x| x.untaint if RUBY_VERSION < "2.7" }
else
rg_full_gem_path
diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
index beff6d658b..a4e1c5a750 100644
--- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
+++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb
@@ -174,7 +174,7 @@ class Bundler::Persistent::Net::HTTP::Persistent
##
# The version of Bundler::Persistent::Net::HTTP::Persistent you are using
- VERSION = '4.0.0'
+ VERSION = '4.0.1'
##
# Error class for errors raised by Bundler::Persistent::Net::HTTP::Persistent. Various
diff --git a/lib/rubygems/ext.rb b/lib/rubygems/ext.rb
index bdd5bd9d82..59fd830437 100644
--- a/lib/rubygems/ext.rb
+++ b/lib/rubygems/ext.rb
@@ -16,3 +16,4 @@ require_relative 'ext/configure_builder'
require_relative 'ext/ext_conf_builder'
require_relative 'ext/rake_builder'
require_relative 'ext/cmake_builder'
+require_relative 'ext/cargo_builder'
diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb
index 6d32be6515..d9aa68c067 100644
--- a/lib/rubygems/ext/builder.rb
+++ b/lib/rubygems/ext/builder.rb
@@ -123,6 +123,9 @@ class Gem::Ext::Builder
Gem::Ext::RakeBuilder
when /CMakeLists.txt/ then
Gem::Ext::CmakeBuilder
+ when /Cargo.toml/ then
+ # We use the spec name here to ensure we invoke the correct init function later
+ Gem::Ext::CargoBuilder.new(@spec)
else
build_error("No builder for extension '#{extension}'")
end
diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb
new file mode 100644
index 0000000000..4c16063224
--- /dev/null
+++ b/lib/rubygems/ext/cargo_builder.rb
@@ -0,0 +1,305 @@
+# frozen_string_literal: true
+
+# This class is used by rubygems to build Rust extensions. It is a thin-wrapper
+# over the `cargo rustc` command which takes care of building Rust code in a way
+# that Ruby can use.
+class Gem::Ext::CargoBuilder < Gem::Ext::Builder
+ attr_reader :spec
+
+ def initialize(spec)
+ @spec = spec
+ end
+
+ def build(_extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd)
+ require "rubygems/command"
+ require "fileutils"
+ require "shellwords"
+
+ build_crate(dest_path, results, args, cargo_dir)
+ ext_path = rename_cdylib_for_ruby_compatibility(dest_path)
+ finalize_directory(ext_path, dest_path, lib_dir, cargo_dir)
+ results
+ end
+
+ private
+
+ def build_crate(dest_path, results, args, cargo_dir)
+ manifest = File.join(cargo_dir, "Cargo.toml")
+
+ given_ruby_static = ENV["RUBY_STATIC"]
+
+ ENV["RUBY_STATIC"] = "true" if ruby_static? && !given_ruby_static
+
+ cargo = ENV.fetch("CARGO", "cargo")
+
+ cmd = []
+ cmd += [cargo, "rustc"]
+ cmd += ["--target-dir", dest_path]
+ cmd += ["--manifest-path", manifest]
+ cmd += ["--lib", "--release", "--locked"]
+ cmd += ["--"]
+ cmd += [*cargo_rustc_args(dest_path)]
+ cmd += Gem::Command.build_args
+ cmd += args
+
+ self.class.run cmd, results, self.class.class_name, cargo_dir
+ results
+ ensure
+ ENV["RUBY_STATIC"] = given_ruby_static
+ end
+
+ def cargo_rustc_args(dest_dir)
+ [
+ *linker_args,
+ *mkmf_libpath,
+ *rustc_dynamic_linker_flags(dest_dir),
+ *rustc_lib_flags(dest_dir),
+ *platform_specific_rustc_args(dest_dir),
+ *debug_flags,
+ ]
+ end
+
+ def platform_specific_rustc_args(dest_dir, flags = [])
+ if mingw_target?
+ # On mingw platforms, mkmf adds libruby to the linker flags
+ flags += libruby_args(dest_dir)
+
+ # Make sure ALSR is used on mingw
+ # see https://github.com/rust-lang/rust/pull/75406/files
+ flags += ["-C", "link-arg=-Wl,--dynamicbase"]
+ flags += ["-C", "link-arg=-Wl,--disable-auto-image-base"]
+
+ # If the gem is installed on a host with build tools installed, but is
+ # run on one that isn't the missing libraries will cause the extension
+ # to fail on start.
+ flags += ["-C", "link-arg=-static-libgcc"]
+ end
+
+ flags
+ end
+
+ # We want to use the same linker that Ruby uses, so that the linker flags from
+ # mkmf work properly.
+ def linker_args
+ # Have to handle CC="cl /nologo" on mswin
+ cc_flag = Shellwords.split(makefile_config("CC"))
+ linker = cc_flag.shift
+ link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] }
+
+ ["-C", "linker=#{linker}", *link_args]
+ end
+
+ def libruby_args(dest_dir)
+ libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED")
+ raw_libs = Shellwords.split(libs)
+ raw_libs.flat_map {|l| ldflag_to_link_modifier(l, dest_dir) }
+ end
+
+ def ruby_static?
+ return true if %w[1 true].include?(ENV["RUBY_STATIC"])
+
+ makefile_config("ENABLE_SHARED") == "no"
+ end
+
+ # Ruby expects the dylib to follow a file name convention for loading
+ def rename_cdylib_for_ruby_compatibility(dest_path)
+ dylib_path = validate_cargo_build!(dest_path)
+ dlext_name = "#{spec.name}.#{makefile_config("DLEXT")}"
+ new_name = dylib_path.gsub(File.basename(dylib_path), dlext_name)
+ FileUtils.cp(dylib_path, new_name)
+ new_name
+ end
+
+ def validate_cargo_build!(dir)
+ prefix = so_ext == "dll" ? "" : "lib"
+ dylib_path = File.join(dir, "release", "#{prefix}#{cargo_crate_name}.#{so_ext}")
+
+ raise DylibNotFoundError, dir unless File.exist?(dylib_path)
+
+ dylib_path
+ end
+
+ def cargo_crate_name
+ spec.metadata.fetch('cargo_crate_name', spec.name).tr('-', '_')
+ end
+
+ def rustc_dynamic_linker_flags(dest_dir)
+ split_flags("DLDFLAGS")
+ .map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir) }
+ .compact
+ .flat_map {|arg| ldflag_to_link_modifier(arg, dest_dir) }
+ end
+
+ def rustc_lib_flags(dest_dir)
+ split_flags("LIBS").flat_map {|arg| ldflag_to_link_modifier(arg, dest_dir) }
+ end
+
+ def split_flags(var)
+ Shellwords.split(RbConfig::CONFIG.fetch(var, ""))
+ end
+
+ def ldflag_to_link_modifier(arg, dest_dir)
+ flag = arg[0..1]
+ val = arg[2..-1]
+
+ case flag
+ when "-L" then ["-L", "native=#{val}"]
+ when "-l" then ["-l", val.to_s]
+ when "-F" then ["-l", "framework=#{val}"]
+ else ["-C", "link_arg=#{arg}"]
+ end
+ end
+
+ def link_flag(link_name)
+ # These are provided by the CRT with MSVC
+ # @see https://github.com/rust-lang/pkg-config-rs/blob/49a4ac189aafa365167c72e8e503565a7c2697c2/src/lib.rs#L622
+ return [] if msvc_target? && ["m", "c", "pthread"].include?(link_name)
+
+ if link_name.include?("ruby")
+ # Specify the lib kind and give it the name "ruby" for linking
+ kind = ruby_static? ? "static" : "dylib"
+
+ ["-l", "#{kind}=ruby:#{link_name}"]
+ else
+ ["-l", link_name]
+ end
+ end
+
+ def msvc_target?
+ makefile_config("target_os").include?("msvc")
+ end
+
+ def darwin_target?
+ makefile_config("target_os").include?("darwin")
+ end
+
+ def mingw_target?
+ makefile_config("target_os").include?("mingw")
+ end
+
+ def win_target?
+ target_platform = RbConfig::CONFIG["target_os"]
+ !!Gem::WIN_PATTERNS.find {|r| target_platform =~ r }
+ end
+
+ # Intepolate substition vars in the arg (i.e. $(DEFFILE))
+ def maybe_resolve_ldflag_variable(input_arg, dest_dir)
+ str = input_arg.gsub(/\$\((\w+)\)/) do |var_name|
+ case var_name
+ # On windows, it is assumed that mkmf has setup an exports file for the
+ # extension, so we have to to create one ourselves.
+ when "DEFFILE"
+ write_deffile(dest_dir)
+ else
+ RbConfig::CONFIG[var_name]
+ end
+ end.strip
+
+ str == "" ? nil : str
+ end
+
+ def write_deffile(dest_dir)
+ deffile_path = File.join(dest_dir, "#{spec.name}-#{RbConfig::CONFIG["arch"]}.def")
+ export_prefix = makefile_config("EXPORT_PREFIX") || ""
+
+ File.open(deffile_path, "w") do |f|
+ f.puts "EXPORTS"
+ f.puts "#{export_prefix.strip}Init_#{spec.name}"
+ end
+
+ deffile_path
+ end
+
+ # We have to basically reimplement RbConfig::CONFIG['SOEXT'] here to support
+ # Ruby < 2.5
+ #
+ # @see https://github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185
+ def so_ext
+ return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT")
+
+ if win_target?
+ "dll"
+ elsif darwin_target?
+ "dylib"
+ else
+ "so"
+ end
+ end
+
+ # Corresponds to $(LIBPATH) in mkmf
+ def mkmf_libpath
+ ["-L", "native=#{makefile_config("libdir")}"]
+ end
+
+ def makefile_config(var_name)
+ val = RbConfig::MAKEFILE_CONFIG[var_name]
+
+ return unless val
+
+ RbConfig.expand(val.dup)
+ end
+
+ # Good balance between binary size and debugability
+ def debug_flags
+ ["-C", "debuginfo=1"]
+ end
+
+ # Copied from ExtConfBuilder
+ def finalize_directory(ext_path, dest_path, lib_dir, extension_dir)
+ require "fileutils"
+ require "tempfile"
+
+ begin
+ tmp_dest = Dir.mktmpdir(".gem.", extension_dir)
+
+ # Some versions of `mktmpdir` return absolute paths, which will break make
+ # if the paths contain spaces. However, on Ruby 1.9.x on Windows, relative
+ # paths cause all C extension builds to fail.
+ #
+ # As such, we convert to a relative path unless we are using Ruby 1.9.x on
+ # Windows. This means that when using Ruby 1.9.x on Windows, paths with
+ # spaces do not work.
+ #
+ # Details: https://github.com/rubygems/rubygems/issues/977#issuecomment-171544940
+ tmp_dest_relative = get_relative_path(tmp_dest.clone, extension_dir)
+
+ if tmp_dest_relative
+ full_tmp_dest = File.join(extension_dir, tmp_dest_relative)
+
+ # TODO: remove in RubyGems 3
+ if Gem.install_extension_in_lib && lib_dir
+ FileUtils.mkdir_p lib_dir
+ FileUtils.cp_r ext_path, lib_dir, remove_destination: true
+ end
+
+ FileUtils::Entry_.new(full_tmp_dest).traverse do |ent|
+ destent = ent.class.new(dest_path, ent.rel)
+ destent.exist? || FileUtils.mv(ent.path, destent.path)
+ end
+ end
+ ensure
+ FileUtils.rm_rf tmp_dest if tmp_dest
+ end
+ end
+
+ def get_relative_path(path, base)
+ path[0..base.length - 1] = "." if path.start_with?(base)
+ path
+ end
+
+ # Error raised when no cdylib artifact was created
+ class DylibNotFoundError < StandardError
+ def initialize(dir)
+ files = Dir.glob(File.join(dir, "**", "*")).map {|f| "- #{f}" }.join "\n"
+
+ super <<~MSG
+ Dynamic library not found for Rust extension (in #{dir})
+
+ Make sure you set "crate-type" in Cargo.toml to "cdylib"
+
+ Found files:
+ #{files}
+ MSG
+ end
+ end
+end
diff --git a/test/rubygems/test_gem_ext_cargo_builder.rb b/test/rubygems/test_gem_ext_cargo_builder.rb
new file mode 100644
index 0000000000..5a1c035947
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+require_relative 'helper'
+require 'rubygems/ext'
+
+class TestGemExtCargoBuilder < Gem::TestCase
+ def setup
+ @orig_env = ENV.to_hash
+
+ @rust_envs = {
+ 'CARGO_HOME' => File.join(@orig_env['HOME'], '.cargo'),
+ 'RUSTUP_HOME' => File.join(@orig_env['HOME'], '.rustup'),
+ }
+
+ system(@rust_envs, 'cargo', '-V', out: IO::NULL, err: [:child, :out])
+ pend 'cargo not present' unless $?.success?
+
+ super
+ end
+
+ def setup_rust_gem(name)
+ @ext = File.join(@tempdir, 'ext')
+ @dest_path = File.join(@tempdir, 'prefix')
+ @fixture_dir = Pathname.new(File.expand_path("../test_gem_ext_cargo_builder/#{name}/", __FILE__))
+
+ FileUtils.mkdir_p @dest_path
+ FileUtils.cp_r(@fixture_dir.to_s, @ext)
+ end
+
+ def test_build_staticlib
+ skip_unsupported_platforms!
+ setup_rust_gem "rust_ruby_example"
+
+ content = @fixture_dir.join('Cargo.toml').read.gsub("cdylib", "staticlib")
+ File.write(File.join(@ext, 'Cargo.toml'), content)
+
+ output = []
+
+ Dir.chdir @ext do
+ ENV.update(@rust_envs)
+ spec = Gem::Specification.new 'rust_ruby_example', '0.1.0'
+ builder = Gem::Ext::CargoBuilder.new(spec)
+ assert_raise(Gem::Ext::CargoBuilder::DylibNotFoundError) do
+ builder.build nil, @dest_path, output
+ end
+ end
+ end
+
+ def test_build_cdylib
+ skip_unsupported_platforms!
+ setup_rust_gem "rust_ruby_example"
+
+ output = []
+
+ Dir.chdir @ext do
+ ENV.update(@rust_envs)
+ spec = Gem::Specification.new 'rust_ruby_example', '0.1.0'
+ builder = Gem::Ext::CargoBuilder.new(spec)
+ builder.build nil, @dest_path, output
+ end
+
+ output = output.join "\n"
+
+ bundle = File.join(@dest_path, "release/rust_ruby_example.#{RbConfig::CONFIG['DLEXT']}")
+
+ require(bundle)
+
+ assert_match RustRubyExample.reverse('hello'), 'olleh'
+
+ assert_match "Compiling rust_ruby_example v0.1.0", output
+ assert_match "Finished release [optimized] target(s)", output
+ rescue Exception => e
+ pp output if output
+
+ raise(e)
+ end
+
+ def test_build_fail
+ skip_unsupported_platforms!
+ setup_rust_gem "rust_ruby_example"
+
+ output = []
+
+ FileUtils.rm(File.join(@ext, 'src/lib.rs'))
+
+ error = assert_raise(Gem::InstallError) do
+ Dir.chdir @ext do
+ ENV.update(@rust_envs)
+ spec = Gem::Specification.new 'rust_ruby_example', '0.1.0'
+ builder = Gem::Ext::CargoBuilder.new(spec)
+ builder.build nil, @dest_path, output
+ end
+ end
+
+ output = output.join "\n"
+
+ assert_match 'cargo failed', error.message
+ end
+
+ def test_full_integration
+ skip_unsupported_platforms!
+ setup_rust_gem "rust_ruby_example"
+
+ Dir.chdir @ext do
+ require 'tmpdir'
+
+ gem = [@rust_envs, *ruby_with_rubygems_in_load_path, File.expand_path("./../../../bin/gem", __FILE__)]
+
+ Dir.mktmpdir("rust_ruby_example") do |dir|
+ built_gem = File.expand_path(File.join(dir, "rust_ruby_example.gem"))
+ Open3.capture2e(*gem, "build", "rust_ruby_example.gemspec", "--output", built_gem)
+ Open3.capture2e(*gem, "install", "--verbose", "--local", built_gem, *ARGV)
+ end
+
+ stdout_and_stderr_str, status = Open3.capture2e(@rust_envs, *ruby_with_rubygems_in_load_path, "-rrust_ruby_example", "-e", "puts 'Result: ' + RustRubyExample.reverse('hello world')")
+
+ assert status.success?, stdout_and_stderr_str
+ assert_match "Result: #{"hello world".reverse}", stdout_and_stderr_str
+ end
+ end
+
+ def test_custom_name
+ skip_unsupported_platforms!
+ setup_rust_gem "custom_name"
+
+ Dir.chdir @ext do
+ require 'tmpdir'
+
+ gem = [@rust_envs, *ruby_with_rubygems_in_load_path, File.expand_path("./../../../bin/gem", __FILE__)]
+
+ Dir.mktmpdir("custom_name") do |dir|
+ built_gem = File.expand_path(File.join(dir, "custom_name.gem"))
+ Open3.capture2e(*gem, "build", "custom_name.gemspec", "--output", built_gem)
+ Open3.capture2e(*gem, "install", "--verbose", "--local", built_gem, *ARGV)
+ end
+
+ stdout_and_stderr_str, status = Open3.capture2e(@rust_envs, *ruby_with_rubygems_in_load_path, "-rcustom_name", "-e", "puts 'Result: ' + CustomName.say_hello")
+
+ assert status.success?, stdout_and_stderr_str
+ assert_match "Result: Hello world!", stdout_and_stderr_str
+ end
+ end
+
+ def skip_unsupported_platforms!
+ pend "jruby not supported" if java_platform?
+ pend "truffleruby not supported (yet)" if RUBY_ENGINE == 'truffleruby'
+ pend "mswin not supported (yet)" if /mswin/ =~ RUBY_PLATFORM && ENV.key?('GITHUB_ACTIONS')
+ end
+end
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/.gitignore b/test/rubygems/test_gem_ext_cargo_builder/custom_name/.gitignore
new file mode 100644
index 0000000000..2f7896d1d1
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock
new file mode 100644
index 0000000000..efee4b2f2c
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.lock
@@ -0,0 +1,374 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bindgen"
+version = "0.59.2"
+source = "git+https://github.com/rust-lang/rust-bindgen?rev=f34e4103f304500c96d332f5cecc9067c980d0f9#f34e4103f304500c96d332f5cecc9067c980d0f9"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
+dependencies = [
+ "atty",
+ "bitflags",
+ "indexmap",
+ "os_str_bytes",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "custom-name-ext"
+version = "0.1.0"
+dependencies = [
+ "rb-sys",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "indexmap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
+
+[[package]]
+name = "libloading"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+ "version_check",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rb-sys"
+version = "0.6.0"
+source = "git+https://github.com/ianks/rb-sys?tag=v0.6.0#1aa5b589e86a14e01aba806511818c19f85d71f6"
+dependencies = [
+ "bindgen",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "shlex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "which"
+version = "4.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2"
+dependencies = [
+ "either",
+ "lazy_static",
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml
new file mode 100644
index 0000000000..e0232f729d
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "custom-name-ext"
+version = "0.1.0"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+# Needed until bindgen has the `allowlist_file` feature
+rb-sys = { git = "https://github.com/ianks/rb-sys", tag = "v0.6.0" }
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb b/test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb
new file mode 100644
index 0000000000..a521dceb4a
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/build.rb
@@ -0,0 +1,21 @@
+if ENV['RUBYOPT'] or defined? Gem
+ ENV.delete 'RUBYOPT'
+
+ require 'rbconfig'
+ cmd = [RbConfig.ruby, '--disable-gems', 'build.rb', *ARGV]
+
+ exec(*cmd)
+end
+
+require 'tmpdir'
+
+lp = File.expand_path("./../../../../../lib", __FILE__)
+gem = ["ruby", "-I#{lp}", File.expand_path("./../../../../../bin/gem", __FILE__)]
+gemspec = File.expand_path("./../custom_name.gemspec", __FILE__)
+
+Dir.mktmpdir("custom_name") do |dir|
+ built_gem = File.expand_path(File.join(dir, "custom_name.gem"))
+ system *gem, "build", gemspec, "--output", built_gem
+ system *gem, "install", "--verbose", "--local", built_gem, *ARGV
+ system %q(ruby -rcustom_name -e "puts 'Result: ' + CustomName.say_hello")
+end
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec b/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec
new file mode 100644
index 0000000000..1ecdae2c33
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/custom_name.gemspec
@@ -0,0 +1,10 @@
+Gem::Specification.new do |s|
+ s.name = "custom_name"
+ s.version = "0.1.0"
+ s.summary = "A Rust extension for Ruby"
+ s.extensions = ["Cargo.toml"]
+ s.authors = ["Ian Ker-Seymer"]
+ s.files = ["Cargo.toml", "Cargo.lock", "src/lib.rs"]
+
+ s.metadata['cargo_crate_name'] = 'custom-name-ext'
+end
diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/src/lib.rs b/test/rubygems/test_gem_ext_cargo_builder/custom_name/src/lib.rs
new file mode 100644
index 0000000000..ca9cf4e656
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/src/lib.rs
@@ -0,0 +1,30 @@
+#[macro_use]
+extern crate rb_sys;
+
+use rb_sys::{rb_define_module, rb_define_module_function, rb_utf8_str_new, VALUE};
+use std::ffi::CString;
+
+ruby_extension!();
+
+#[no_mangle]
+unsafe extern "C" fn say_hello(_klass: VALUE) -> VALUE {
+ let cstr = CString::new("Hello world!").unwrap();
+
+ rb_utf8_str_new(cstr.as_ptr(), 12)
+}
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn Init_custom_name() {
+ let name = CString::new("CustomName").unwrap();
+ let function_name = CString::new("say_hello").unwrap();
+ // bindgen does not properly detect the arity of the ruby callback function, so we have to transmute
+ let callback = unsafe {
+ std::mem::transmute::<unsafe extern "C" fn(VALUE) -> VALUE, unsafe extern "C" fn() -> VALUE>(
+ say_hello,
+ )
+ };
+ let klass = unsafe { rb_define_module(name.as_ptr()) };
+
+ unsafe { rb_define_module_function(klass, function_name.as_ptr(), Some(callback), 0) }
+}
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/.gitignore b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/.gitignore
new file mode 100644
index 0000000000..2f7896d1d1
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
new file mode 100644
index 0000000000..9248f7101c
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock
@@ -0,0 +1,374 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bindgen"
+version = "0.59.2"
+source = "git+https://github.com/rust-lang/rust-bindgen?rev=f34e4103f304500c96d332f5cecc9067c980d0f9#f34e4103f304500c96d332f5cecc9067c980d0f9"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "3.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
+dependencies = [
+ "atty",
+ "bitflags",
+ "indexmap",
+ "os_str_bytes",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "indexmap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
+
+[[package]]
+name = "libloading"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+ "version_check",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rb-sys"
+version = "0.6.0"
+source = "git+https://github.com/ianks/rb-sys?tag=v0.6.0#1aa5b589e86a14e01aba806511818c19f85d71f6"
+dependencies = [
+ "bindgen",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "rust_ruby_example"
+version = "0.1.0"
+dependencies = [
+ "rb-sys",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "shlex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "which"
+version = "4.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2"
+dependencies = [
+ "either",
+ "lazy_static",
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
new file mode 100644
index 0000000000..66a98f3885
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "rust_ruby_example"
+version = "0.1.0"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+# Needed until bindgen has the `allowlist_file` feature
+rb-sys = { git = "https://github.com/ianks/rb-sys", tag = "v0.6.0" }
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb
new file mode 100644
index 0000000000..468274c9a7
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/build.rb
@@ -0,0 +1,21 @@
+if ENV['RUBYOPT'] or defined? Gem
+ ENV.delete 'RUBYOPT'
+
+ require 'rbconfig'
+ cmd = [RbConfig.ruby, '--disable-gems', 'build.rb', *ARGV]
+
+ exec(*cmd)
+end
+
+require 'tmpdir'
+
+lp = File.expand_path("./../../../../../lib", __FILE__)
+gem = ["ruby", "-I#{lp}", File.expand_path("./../../../../../bin/gem", __FILE__)]
+gemspec = File.expand_path("./../rust_ruby_example.gemspec", __FILE__)
+
+Dir.mktmpdir("rust_ruby_example") do |dir|
+ built_gem = File.expand_path(File.join(dir, "rust_ruby_example.gem"))
+ system *gem, "build", gemspec, "--output", built_gem
+ system *gem, "install", "--verbose", "--local", built_gem, *ARGV
+ system %q(ruby -rrust_ruby_example -e "puts 'Result: ' + RustRubyExample.reverse('hello world')")
+end
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/rust_ruby_example.gemspec b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/rust_ruby_example.gemspec
new file mode 100644
index 0000000000..82c84ba818
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/rust_ruby_example.gemspec
@@ -0,0 +1,8 @@
+Gem::Specification.new do |s|
+ s.name = "rust_ruby_example"
+ s.version = "0.1.0"
+ s.summary = "A Rust extension for Ruby"
+ s.extensions = ["Cargo.toml"]
+ s.authors = ["Ian Ker-Seymer"]
+ s.files = ["Cargo.toml", "Cargo.lock", "src/lib.rs"]
+end
diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/src/lib.rs b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/src/lib.rs
new file mode 100644
index 0000000000..26bddaeaf1
--- /dev/null
+++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/src/lib.rs
@@ -0,0 +1,42 @@
+#[macro_use]
+extern crate rb_sys;
+
+use rb_sys::{
+ rb_define_module, rb_define_module_function, rb_string_value_cstr, rb_utf8_str_new, VALUE,
+};
+use std::ffi::{CStr, CString};
+use std::os::raw::{c_char, c_long};
+
+ruby_extension!();
+
+#[inline]
+unsafe fn cstr_to_string(str: *const c_char) -> String {
+ CStr::from_ptr(str).to_string_lossy().into_owned()
+}
+
+#[no_mangle]
+unsafe extern "C" fn pub_reverse(_klass: VALUE, mut input: VALUE) -> VALUE {
+ let ruby_string = cstr_to_string(rb_string_value_cstr(&mut input));
+ let reversed = ruby_string.to_string().chars().rev().collect::<String>();
+ let reversed_cstring = CString::new(reversed).unwrap();
+ let size = ruby_string.len() as c_long;
+
+ rb_utf8_str_new(reversed_cstring.as_ptr(), size)
+}
+
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn Init_rust_ruby_example() {
+ let name = CString::new("RustRubyExample").unwrap();
+ let function_name = CString::new("reverse").unwrap();
+ // bindgen does not properly detect the arity of the ruby callback function, so we have to transmute
+ let callback = unsafe {
+ std::mem::transmute::<
+ unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
+ unsafe extern "C" fn() -> VALUE,
+ >(pub_reverse)
+ };
+ let klass = unsafe { rb_define_module(name.as_ptr()) };
+
+ unsafe { rb_define_module_function(klass, function_name.as_ptr(), Some(callback), 1) }
+}