1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
require_relative "cargo/metadata"
require_relative "error"
begin
require "rake/extensiontask"
rescue LoadError
abort "Please install rake-compiler to use this feature"
end
module RbSys
# ExtensionTask is a Rake::ExtensionTask subclass that is used to tailored for
# Rust extensions. It has the same options a `Rake::ExtensionTask`.
#
# @see https://www.rubydoc.info/gems/rake-compiler/Rake/ExtensionTask
#
# @example
# RbSys::ExtensionTask.new("my-crate", my_gemspec) do |ext|
# ext.lib_dir = "lib/my-crate"
# end
#
# @param name [String] the crate name to build
# @param gem_spec [Gem::Specification] the gem specification to build (needed for cross-compiling)
# @return [Rake::ExtensionTask]
class ExtensionTask < Rake::ExtensionTask
def initialize(name = nil, gem_spec = :undefined)
super
end
def init(name = nil, gem_spec = nil)
super(name, lint_gem_spec(name, gem_spec))
@orginal_ext_dir = @ext_dir
@ext_dir = cargo_metadata.manifest_directory
@source_pattern = nil
@compiled_pattern = "*.{obj,so,bundle,dSYM}"
@cross_compile = ENV.key?("RUBY_TARGET")
@cross_platform = [ENV["RUBY_TARGET"]].compact
@cross_compiling_blocks = []
@cross_compiling_blocks << proc do |gemspec|
warn "Removing unneeded dependencies from native gemspec"
gemspec.dependencies.reject! { |d| d.name == "rb_sys" }
end
@cross_compiling_blocks << proc do |gemspec|
warn "Removing source files from native gemspec"
gemspec.files.reject! { |f| f.end_with?(".rs") }
gemspec.files.reject! { |f| f.match?(/Cargo.(toml|lock)$/) }
gemspec.files.reject! { |f| extconf.end_with?(f) }
gemspec.extensions.reject! { |f| f.end_with?("Cargo.toml") }
end
end
def define
super
define_env_tasks
CLEAN.include(target_directory) if defined?(CLEAN)
end
def cargo_metadata
@cargo_metadata ||= Cargo::Metadata.new(@name)
end
def extconf
File.join(cargo_metadata.manifest_directory, "extconf.rb")
end
def binary(_platf)
super.tr("-", "_")
end
# I'm not sure why this is necessary, can it be removed?
def source_files
list = FileList[
"#{ext_dir}/**/*.{rs,rb,c,h,toml}",
"**/Cargo.{toml,lock}",
"**/.cargo/**/*",
"#{ext_dir}/lib/**/*"
]
list.include("#{ext_dir}/#{@source_pattern}") if @source_pattern
list.exclude(File.join(target_directory, "**/*"))
list
end
def cross_compiling(&block)
@cross_compiling_blocks << block if block
end
def target_directory
cargo_metadata.target_directory
end
def define_native_tasks(for_platform = nil, ruby_ver = RUBY_VERSION, callback = nil)
cb = proc do |gemspec|
callback&.call(gemspec)
@cross_compiling_blocks.each do |block|
block.call(gemspec)
end
end
super(for_platform, ruby_ver, cb)
end
def define_env_tasks
task "rb_sys:env:default" do
ENV["RB_SYS_CARGO_TARGET_DIR"] ||= target_directory
ENV["RB_SYS_CARGO_MANIFEST_DIR"] ||= cargo_metadata.manifest_directory
ENV["RB_SYS_CARGO_PROFILE"] ||= "release"
end
desc "Use the debug profile for building native Rust extensions"
task "rb_sys:env:dev" do
ENV["RB_SYS_CARGO_PROFILE"] = "dev"
end
desc "Use the release profile for building native Rust extensions"
task "rb_sys:env:release" do
ENV["RB_SYS_CARGO_PROFILE"] = "release"
end
file extconf => "rb_sys:env:default"
desc 'Compile the native Rust extension with the "dev" profile'
task "compile:dev" => ["rb_sys:env:dev", "compile"]
desc 'Compile the native Rust extension with the "release" profile'
task "compile:release" => ["rb_sys:env:release", "compile"]
end
private
def lint_gem_spec(name, gs)
gem_spec = case gs
when :undefined
return
when Gem::Specification
gs
when String
Gem::Specification.load(gem_spec) || raise(ArgumentError, "Unable to load gemspec from file #{gs.inspect}")
else
raise ArgumentError, "gem_spec must be a Gem::Specification, got #{gs.class}"
end
gem_spec.files.each do |f|
if /\.(dll|so|dylib|lib|bundle)$/.match?(f)
warn "⚠️ gemspec includes native artifact (#{f}), please remove it."
end
end
if (gem_crate_name = gem_spec.metadata["cargo_crate_name"])
if name != gem_crate_name
warn "⚠️ cargo_crate_name (#{gem_crate_name}) does not match extension task crate name (#{name})"
end
end
gem_spec
end
end
end
|