# 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