module RbSys::Mkmf
def assert_libclang_version_valid!
def assert_libclang_version_valid! libclang_version = Gem::Version.new(Libclang.version) if libclang_version < Gem::Version.new("5.0.0") raise "libclang version 5.0.0 or greater is required (current #{libclang_version})" end if libclang_version >= Gem::Version.new("17.0.0") raise "libclang version < 17.0.0 or greater is required (current #{libclang_version})" end end
def assign_stmt(a, b, indent: 0)
def assign_stmt(a, b, indent: 0) if $nmake "#{a} = #{b}" else "#{"\t" * indent}#{a} = #{b}" end end
def base_makefile(cargo_dir)
def base_makefile(cargo_dir) base_makefile = dummy_makefile(__dir__).join("\n") base_makefile.gsub!("all install static install-so install-rb", "all static install-rb") base_makefile.gsub!(/^srcdir = .*$/, "srcdir = #{cargo_dir}") base_makefile end
def cargo_command(cargo_dir, builder)
def cargo_command(cargo_dir, builder) builder.ext_dir = cargo_dir dest_path = builder.target_dir || File.join(Dir.pwd, "target") args = ARGV.dup args.shift if args.first == "--" cargo_cmd = builder.cargo_command(dest_path, args) cmd = Shellwords.join(cargo_cmd) cmd.gsub!("\\=", "=") cmd.gsub!(/\Acargo rustc/, "$(CARGO) rustc $(RB_SYS_EXTRA_CARGO_ARGS) --manifest-path $(RB_SYS_CARGO_MANIFEST_DIR)/Cargo.toml") cmd.gsub!(/-v=\d/, "") cmd end
def conditional_assign(a, b, export: false, indent: 0)
def conditional_assign(a, b, export: false, indent: 0) if $nmake result = +"!IFNDEF #{a}\n#{a} = #{b}\n!ENDIF\n" result << export_env(a, b) if export result else "#{"\t" * indent}#{export ? "export " : ""}#{a} ?= #{b}" end end
def create_rust_makefile(target, &blk)
- Example: Configure a custom build profile -
Example: Basic -
def create_rust_makefile(target, &blk) if target.include?("/") target_prefix, target = File.split(target) target_prefix[0, 0] = "/" else target_prefix = "" end spec = Struct.new(:name, :metadata).new(target, {}) cargo_builder = CargoBuilder.new(spec) builder = Config.new(cargo_builder) yield builder if blk srcprefix = File.join("$(srcdir)", builder.ext_dir.gsub(/\A\.\/?/, "")).chomp("/") RbConfig.expand(srcdir = srcprefix.dup) full_cargo_command = cargo_command(srcdir, builder) global_rustflags = GLOBAL_RUSTFLAGS.dup global_rustflags << "--cfg=rb_sys_use_stable_api_compiled_fallback" if builder.use_stable_api_compiled_fallback? make_install = +<<~MAKE #{conditional_assign("RB_SYS_BUILD_DIR", File.join(Dir.pwd, ".rb-sys"))} #{conditional_assign("CARGO", "cargo")} #{conditional_assign("CARGO_BUILD_TARGET", builder.target)} #{conditional_assign("SOEXT", builder.so_ext)} #{try_load_bundled_libclang(builder)} # Determine the prefix Cargo uses for the lib. #{if_neq_stmt("$(SOEXT)", "dll")} #{conditional_assign("SOEXT_PREFIX", "lib", indent: 1)} #{endif_stmt} #{set_cargo_profile(builder)} #{conditional_assign("RB_SYS_CARGO_FEATURES", builder.features.join(","))} #{conditional_assign("RB_SYS_GLOBAL_RUSTFLAGS", global_rustflags.join(" "))} #{conditional_assign("RB_SYS_EXTRA_RUSTFLAGS", builder.extra_rustflags.join(" "))} #{conditional_assign("RB_SYS_EXTRA_CARGO_ARGS", builder.extra_cargo_args.join(" "))} #{conditional_assign("RB_SYS_CARGO_MANIFEST_DIR", builder.manifest_dir)} # Set dirname for the profile, since the profiles do not directly map to target dir (i.e. dev -> debug) #{if_eq_stmt("$(RB_SYS_CARGO_PROFILE)", "dev")} #{conditional_assign("RB_SYS_CARGO_PROFILE_DIR", "debug", indent: 1)} #{else_stmt} #{conditional_assign("RB_SYS_CARGO_PROFILE_DIR", "$(RB_SYS_CARGO_PROFILE)", indent: 1)} #{endif_stmt} # Set the build profile (dev, release, etc.). #{assign_stmt("RB_SYS_CARGO_PROFILE_FLAG", "--profile $(RB_SYS_CARGO_PROFILE)", indent: 1)} # Account for sub-directories when using `--target` argument with Cargo #{conditional_assign("RB_SYS_CARGO_TARGET_DIR", "target")} #{if_neq_stmt("$(CARGO_BUILD_TARGET)", "")} #{assign_stmt("RB_SYS_FULL_TARGET_DIR", "$(RB_SYS_CARGO_TARGET_DIR)/$(CARGO_BUILD_TARGET)", indent: 1)} #{else_stmt} #{assign_stmt("RB_SYS_FULL_TARGET_DIR", "$(RB_SYS_CARGO_TARGET_DIR)", indent: 1)} #{endif_stmt} target_prefix = #{target_prefix} TARGET_NAME = #{target[/\A\w+/]} TARGET_ENTRY = #{RbConfig::CONFIG["EXPORT_PREFIX"]}Init_$(TARGET_NAME) RUBYARCHDIR = $(sitearchdir)$(target_prefix) TARGET = #{target} DLLIB = $(TARGET).#{RbConfig::CONFIG["DLEXT"]} RUSTLIBDIR = $(RB_SYS_FULL_TARGET_DIR)/$(RB_SYS_CARGO_PROFILE_DIR) RUSTLIB = $(RUSTLIBDIR)/$(SOEXT_PREFIX)$(TARGET_NAME).$(SOEXT) TIMESTAMP_DIR = . CLEANOBJS = $(RUSTLIBDIR) $(RB_SYS_BUILD_DIR) CLEANLIBS = $(DLLIB) $(RUSTLIB) RUBYGEMS_CLEAN_DIRS = $(CLEANOBJS) $(CLEANFILES) #{builder.rubygems_clean_dirs.join(" ")} #{base_makefile(srcdir)} .PHONY: gemclean #{if_neq_stmt("$(RB_SYS_VERBOSE)", "")} #{assign_stmt("Q", "$(0=@)", indent: 1)} #{endif_stmt} #{env_vars(builder)} #{export_env("RUSTFLAGS", "$(RB_SYS_GLOBAL_RUSTFLAGS) $(RB_SYS_EXTRA_RUSTFLAGS) $(RUSTFLAGS)")} FORCE: ; #{optional_rust_toolchain(builder)} #{timestamp_file("sitearchdir")}: \t$(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) \t$(Q) $(TOUCH) $@ $(RUSTLIB): FORCE \t$(ECHO) generating $(@) \\("$(RB_SYS_CARGO_PROFILE)"\\) \t#{full_cargo_command} $(DLLIB): $(RUSTLIB) \t$(Q) $(COPY) "$(RUSTLIB)" $@ install-so: $(DLLIB) #{timestamp_file("sitearchdir")} \t$(ECHO) installing $(DLLIB) to $(RUBYARCHDIR) \t$(Q) $(MAKEDIRS) $(RUBYARCHDIR) \t$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) gemclean: \t$(ECHO) Cleaning gem artifacts \t-$(Q)$(RM_RF) $(RUBYGEMS_CLEAN_DIRS) 2> /dev/null || true install: #{builder.clean_after_install ? "install-so gemclean" : "install-so"} all: #{$extout ? "install" : "$(DLLIB)"} MAKE gsub_cargo_command!(make_install, builder: builder) File.write("Makefile", make_install) end
def else_stmt
def else_stmt if $nmake "!ELSE" else "else" end end
def endif_stmt
def endif_stmt if $nmake "!ENDIF" else "endif" end end
def env_line(k, v)
def env_line(k, v) return unless v export_env(k, strip_cmd(v.gsub("\n", '\n'))) end
def env_or_makefile_config(key, builder)
def env_or_makefile_config(key, builder) builder.env[key] || ENV[key] || RbConfig::MAKEFILE_CONFIG[key] end
def env_vars(builder)
def env_vars(builder) lines = [] if (cc = env_or_makefile_config("CC", builder)) && find_executable(cc) lines << assign_stmt("CC", cc) end if (cxx = env_or_makefile_config("CXX", builder)) && find_executable(cxx) lines << assign_stmt("CXX", cxx) end if (ar = env_or_makefile_config("AR", builder)) && find_executable(ar) lines << assign_stmt("AR", ar) end lines += builder.build_env.map { |k, v| env_line(k, v) } lines.compact.join("\n") end
def export_env(k, v)
def export_env(k, v) if $nmake "!if [set #{k}=#{v}]\n!endif" else "export #{k} := #{v}" end end
def force_install_rust_toolchain?(builder)
def force_install_rust_toolchain?(builder) return builder.force_install_rust_toolchain if builder.force_install_rust_toolchain return false unless builder.rubygems_invoked? && builder.auto_install_rust_toolchain find_executable("cargo").nil? end
def gsub_cargo_command!(cargo_command, builder:)
def gsub_cargo_command!(cargo_command, builder:) cargo_command.gsub!(/--profile \w+/, "$(RB_SYS_CARGO_PROFILE_FLAG)") cargo_command.gsub!(%r{--features \S+}, "--features $(RB_SYS_CARGO_FEATURES)") cargo_command.gsub!(%r{--target \S+}, "--target $(CARGO_BUILD_TARGET)") cargo_command.gsub!(/--target-dir (?:(?!--).)+/, "--target-dir $(RB_SYS_CARGO_TARGET_DIR) ") cargo_command end
def if_eq_stmt(a, b)
def if_eq_stmt(a, b) if $nmake "!IF #{a.inspect} == #{b.inspect}" else "ifeq (#{a},#{b})" end end
def if_neq_stmt(a, b)
def if_neq_stmt(a, b) if $nmake "!IF #{a.inspect} != #{b.inspect}" else "ifneq (#{a},#{b})" end end
def install_extra_rustup_targets(builder)
def install_extra_rustup_targets(builder) builder.extra_rustup_targets.map do |target| "\t$(Q) $(CARGO_HOME)/bin/rustup target add #{target}" end.join("\n") end
def optional_rust_toolchain(builder)
def optional_rust_toolchain(builder) <<~MAKE #{conditional_assign("RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN", force_install_rust_toolchain?(builder))} # Only run if the we are told to explicitly install the Rust toolchain #{if_neq_stmt("$(RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN)", "false")} #{rust_toolchain_env(builder)} $(CARGO): \t$(Q) $(MAKEDIRS) $(CARGO_HOME) $(RUSTUP_HOME) \t$(Q) curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused -fsSL "https://sh.rustup.rs" | sh -s -- --no-modify-path --profile $(RB_SYS_RUSTUP_PROFILE) --default-toolchain none -y \t$(Q) $(CARGO_HOME)/bin/rustup toolchain install $(RB_SYS_DEFAULT_TOOLCHAIN) --profile $(RB_SYS_RUSTUP_PROFILE) \t$(Q) $(CARGO_HOME)/bin/rustup default $(RB_SYS_DEFAULT_TOOLCHAIN) #{install_extra_rustup_targets(builder)} $(RUSTLIB): $(CARGO) #{endif_stmt} MAKE end
def rust_toolchain_env(builder)
def rust_toolchain_env(builder) <<~MAKE #{conditional_assign("RB_SYS_RUSTUP_PROFILE", "minimal")} # If the user passed true, we assume stable Rust. Otherwise, use what # was specified (i.e. RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN=beta) #{if_eq_stmt("$(RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN)", "true")} RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN = stable #{endif_stmt} # If a $RUST_TARGET is specified (i.e. for rake-compiler-dock), append # that to the profile. #{if_eq_stmt("$(RUST_TARGET)", "")} RB_SYS_DEFAULT_TOOLCHAIN = $(RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN) #{else_stmt} RB_SYS_DEFAULT_TOOLCHAIN = $(RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN)-$(RUST_TARGET) #{endif_stmt} # Since we are forcing the installation of the Rust toolchain, we need # to set these env vars unconditionally for the build. #{export_env("CARGO_HOME", "$(RB_SYS_BUILD_DIR)/$(RB_SYS_DEFAULT_TOOLCHAIN)/cargo")} #{export_env("RUSTUP_HOME", "$(RB_SYS_BUILD_DIR)/$(RB_SYS_DEFAULT_TOOLCHAIN)/rustup")} #{export_env("PATH", "$(CARGO_HOME)/bin:$(RUSTUP_HOME)/bin:$(PATH)")} #{export_env("RUSTUP_TOOLCHAIN", "$(RB_SYS_DEFAULT_TOOLCHAIN)")} #{export_env("CARGO", "$(CARGO_HOME)/bin/cargo")} MAKE end
def set_cargo_profile(builder)
def set_cargo_profile(builder) return assign_stmt("RB_SYS_CARGO_PROFILE", "release") if builder.rubygems_invoked? conditional_assign("RB_SYS_CARGO_PROFILE", builder.profile) end
def strip_cmd(cmd)
def strip_cmd(cmd) cmd.gsub("-nologo", "").strip end
def try_load_bundled_libclang(_builder)
def try_load_bundled_libclang(_builder) require "libclang" assert_libclang_version_valid! export_env("LIBCLANG_PATH", Libclang.libdir) rescue LoadError # If we can't load the bundled libclang, just continue end