class Gem::Ext::CargoBuilder
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.
Attributes
spec[R]
Public Class Methods
new(spec)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 9 def initialize(spec) @spec = spec end
Public Instance Methods
build(_extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 13 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 Instance Methods
build_crate(dest_path, results, args, cargo_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 26 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
cargo_crate_name()
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 122 def cargo_crate_name spec.metadata.fetch('cargo_crate_name', spec.name).tr('-', '_') end
cargo_rustc_args(dest_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 51 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
darwin_target?()
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 172 def darwin_target? makefile_config("target_os").include?("darwin") end
debug_flags()
click to toggle source
Good balance between binary size and debugability
# File lib/rubygems/ext/cargo_builder.rb, line 243 def debug_flags ["-C", "debuginfo=1"] end
finalize_directory(ext_path, dest_path, lib_dir, extension_dir)
click to toggle source
Copied from ExtConfBuilder
# File lib/rubygems/ext/cargo_builder.rb, line 248 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
get_relative_path(path, base)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 285 def get_relative_path(path, base) path[0..base.length - 1] = "." if path.start_with?(base) path end
ldflag_to_link_modifier(arg, dest_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 141 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
libruby_args(dest_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 92 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
link_flag(link_name)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 153 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
linker_args()
click to toggle source
We want to use the same linker that Ruby uses, so that the linker flags from mkmf work properly.
# File lib/rubygems/ext/cargo_builder.rb, line 83 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
makefile_config(var_name)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 234 def makefile_config(var_name) val = RbConfig::MAKEFILE_CONFIG[var_name] return unless val RbConfig.expand(val.dup) end
maybe_resolve_ldflag_variable(input_arg, dest_dir)
click to toggle source
Intepolate substition vars in the arg (i.e. $(DEFFILE))
# File lib/rubygems/ext/cargo_builder.rb, line 186 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
mingw_target?()
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 176 def mingw_target? makefile_config("target_os").include?("mingw") end
mkmf_libpath()
click to toggle source
Corresponds to $(LIBPATH) in mkmf
# File lib/rubygems/ext/cargo_builder.rb, line 230 def mkmf_libpath ["-L", "native=#{makefile_config("libdir")}"] end
msvc_target?()
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 168 def msvc_target? makefile_config("target_os").include?("msvc") end
platform_specific_rustc_args(dest_dir, flags = [])
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 62 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
rename_cdylib_for_ruby_compatibility(dest_path)
click to toggle source
Ruby expects the dylib to follow a file name convention for loading
# File lib/rubygems/ext/cargo_builder.rb, line 105 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
ruby_static?()
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 98 def ruby_static? return true if %w[1 true].include?(ENV["RUBY_STATIC"]) makefile_config("ENABLE_SHARED") == "no" end
rustc_dynamic_linker_flags(dest_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 126 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
rustc_lib_flags(dest_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 133 def rustc_lib_flags(dest_dir) split_flags("LIBS").flat_map {|arg| ldflag_to_link_modifier(arg, dest_dir) } end
so_ext()
click to toggle source
We have to basically reimplement RbConfig::CONFIG here to support Ruby < 2.5
@see github.com/ruby/ruby/blob/c87c027f18c005460746a74c07cd80ee355b16e4/configure.ac#L3185
# File lib/rubygems/ext/cargo_builder.rb, line 217 def so_ext return RbConfig::CONFIG["SOEXT"] if RbConfig::CONFIG.key?("SOEXT") if win_target? "dll" elsif darwin_target? "dylib" else "so" end end
split_flags(var)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 137 def split_flags(var) Shellwords.split(RbConfig::CONFIG.fetch(var, "")) end
validate_cargo_build!(dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 113 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
win_target?()
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 180 def win_target? target_platform = RbConfig::CONFIG["target_os"] !!Gem::WIN_PATTERNS.find {|r| target_platform =~ r } end
write_deffile(dest_dir)
click to toggle source
# File lib/rubygems/ext/cargo_builder.rb, line 201 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