# frozen_string_literal: true
require "shellwords"
class ThreadIdRakeCargoHelper
attr_reader :gemname
def initialize(gemname=File.basename(__dir__))
@gemname = gemname
end
def self.command?(name)
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
ENV["PATH"].split(File::PATH_SEPARATOR).any? do |path|
exts.any? do |ext|
exe = File.join(path, "#{name}#{ext}")
File.executable?(exe) && !File.directory?(exe)
end
end
end
def self.rust_toolchain
# return env variable if set
target = ENV["RUST_TARGET"]
return target if target
str = `rustc --version --verbose`
info = str.lines.map {|l| l.chomp.split(/:\s+/, 2)}.drop(1).to_h
info["host"]
end
def self.cargo_target_dir
return @cargo_target_dir if defined? @cargo_target_dir
str = `cargo metadata --format-version 1 --offline --no-deps --quiet`
begin
require "json"
dir = JSON.parse(str)["target_directory"]
rescue LoadError # json is usually part of the stdlib, but just in case
/"target_directory"\s*:\s*"(?
[^"]*)"/ =~ str
end
@cargo_target_dir = dir || "target"
end
def self.flags
cc_flags = Shellwords.split(RbConfig.expand(RbConfig::MAKEFILE_CONFIG["CC"].dup))
["-C", "linker=#{cc_flags.shift}",
*cc_flags.flat_map {|a| ["-C", "link-arg=#{a}"] },
"-L", "native=#{RbConfig::CONFIG["libdir"]}",
*dld_flags,
*platform_flags]
end
def self.dld_flags
Shellwords.split(RbConfig::CONFIG["DLDFLAGS"]).flat_map do |arg|
arg = arg.gsub(/\$\((\w+)\)/) do
$1 == "DEFFILE" ? nil : RbConfig::CONFIG[name]
end.strip
next [] if arg.empty?
transform_flag(arg)
end
end
def self.platform_flags
return unless RbConfig::CONFIG["target_os"] =~ /mingw/i
[*Shellwords.split(RbConfig::CONFIG["LIBRUBYARG"]).flat_map {|arg| transform_flag(arg)},
"-C", "link-arg=-Wl,--dynamicbase",
"-C", "link-arg=-Wl,--disable-auto-image-base",
"-C", "link-arg=-static-libgcc"]
end
def self.transform_flag(arg)
k, v = arg.split(/(?<=..)/, 2)
case k
when "-L"
[k, "native=#{v}"]
when "-l"
[k, v]
when "-F"
["-l", "framework=#{v}"]
else
["-C", "link_arg=#{k}#{v}"]
end
end
def install_dir
File.expand_path(File.join("..", "..", "lib", gemname), __dir__)
end
def rust_name
prefix = "lib" unless Gem.win_platform?
suffix = if RbConfig::CONFIG["target_os"] =~ /darwin/i
".dylib"
elsif Gem.win_platform?
".dll"
else
".so"
end
"#{prefix}#{gemname}#{suffix}"
end
def ruby_name
"#{gemname}.#{RbConfig::CONFIG["DLEXT"]}"
end
end
task default: [:thread_id_install, :thread_id_clean]
task thread_id: [:thread_id_install, :thread_id_clean]
desc "set dev mode for subsequent task, run like `rake dev install`"
task :thread_id_dev do
@dev = true
end
desc "build gem native extension and copy to lib"
task thread_id_install: [:thread_id_cd, :thread_id_build] do
helper = ThreadIdRakeCargoHelper.new
profile_dir = @dev ? "debug" : "release"
arch_dir = RbspyRakeCargoHelper.rust_toolchain
source = File.join(ThreadIdRakeCargoHelper.cargo_target_dir, arch_dir, profile_dir, helper.rust_name)
dest = File.join(helper.install_dir, helper.ruby_name)
mkdir_p(helper.install_dir)
rm(dest) if File.exist?(dest)
cp(source, dest)
end
desc "build gem native extension"
task thread_id_build: [:thread_id_cargo, :thread_id_cd] do
sh "cargo", "rustc", *(["--locked", "--release"] unless @dev), "--target=#{RbspyRakeCargoHelper.rust_toolchain}", "--", *RbspyRakeCargoHelper.flags
end
desc "clean up release build artifacts"
task thread_id_clean: [:thread_id_cargo, :thread_id_cd] do
sh "cargo clean --release"
end
desc "clean up build artifacts"
task thread_id_clobber: [:thread_id_cargo, :thread_id_cd] do
sh "cargo clean"
end
desc "check for cargo"
task :thread_id_cargo do
raise <<-MSG unless ThreadIdRakeCargoHelper.command?("cargo")
This gem requires a Rust compiler and the `cargo' build tool to build the
gem's native extension. See https://www.rust-lang.org/tools/install for
how to install Rust. `cargo' is usually part of the Rust installation.
MSG
raise <<-MSG if Gem.win_platform? && ThreadIdRakeCargoHelper.rust_toolchain !~ /gnu/
Found Rust toolchain `#{ThreadIdRakeCargoHelper.rust_toolchain}' but the gem native
extension requires the gnu toolchain on Windows.
MSG
end
# ensure task is running in the right dir
task :thread_id_cd do
cd(__dir__) unless __dir__ == pwd
end