| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| # frozen_string_literal: true |
| |
| require "bundler/gem_tasks" |
| require "fileutils" |
| require "rake/testtask" |
| require "rubygems/ext" |
| require "rubygems/ext/cargo_builder" |
| require "rubygems/package" |
| require "standard/rake" |
| |
| GEMSPEC = Gem::Specification.load("opendal.gemspec") |
| PACKAGE_NAME = "opendal" |
| CRATE_PACKAGE_NAME = "opendal-ruby" |
| # Ruby loads native extensions by basename without the platform suffix. |
| # RbConfig::CONFIG["DLEXT"] is Ruby's loadable native-extension suffix for |
| # the current platform, such as "bundle" on macOS or "so" on Linux. |
| EXTENSION = "opendal_ruby" |
| EXTENSION_FILE = "#{EXTENSION}.#{RbConfig::CONFIG["DLEXT"]}" |
| EXTENSION_PATH = "lib/#{EXTENSION_FILE}" |
| |
| desc "Copy core files for compilation" |
| task :copy_core do |
| core_dir = "../../core" |
| distributed_core_dir = "core" |
| |
| puts "Copying core files from #{core_dir} to #{distributed_core_dir}..." |
| FileUtils.rm_rf(distributed_core_dir, secure: true) |
| system("cp", "-Lr", core_dir, distributed_core_dir) # resolves symbolic links to a copy |
| |
| # Verify core files were copied |
| core_files_count = `git -C #{File.expand_path(core_dir, __dir__)} ls-files -z`.split("\x0").length |
| puts "Copied #{core_files_count} files from core directory" |
| |
| # Patch Cargo.toml for source distribution |
| # When core files are copied locally, update Cargo.toml to reference the local copy |
| if File.directory?(distributed_core_dir) |
| cargo_toml = File.read("Cargo.toml") |
| |
| # Only patch if we haven't already and we're using the relative path |
| if cargo_toml.include?('path = "../../core"') |
| puts "Patching Cargo.toml to use local core directory..." |
| cargo_toml.gsub!('path = "../../core"', 'path = "core"') |
| File.write("Cargo.toml", cargo_toml) |
| end |
| end |
| end |
| |
| Rake::TestTask.new("test:base") do |t| |
| t.libs << "lib" |
| t.libs << "test" |
| t.test_files = FileList["test/*_test.rb"] |
| end |
| |
| Rake::TestTask.new("test:service") do |t| |
| t.libs << "lib" |
| t.libs << "test" |
| t.test_files = FileList["test/service/*_test.rb"] |
| end |
| |
| desc "Test base functionality" |
| task "test:base" => :test |
| |
| desc "Test service functionality" |
| task "test:service" => :test |
| |
| namespace :doc do |
| task default: %i[rustdoc yard] |
| |
| desc "Generate YARD documentation" |
| task :yard do |
| run("bundle exec yard doc --plugin rustdoc -- lib tmp/doc/opendal_ruby.json") |
| end |
| |
| desc "Generate Rust documentation as JSON" |
| task :rustdoc do |
| target_dir = "tmp" |
| ext_dir = "opendal-ruby" |
| run(<<~CMD) |
| cargo +nightly-2025-10-12 rustdoc \ |
| --target-dir #{target_dir} \ |
| --package #{ext_dir} \ |
| -Zunstable-options \ |
| --output-format json \ |
| --lib \ |
| -- \ |
| --document-private-items |
| CMD |
| end |
| |
| def run(cmd) |
| system(cmd) |
| fail if $? != 0 |
| end |
| end |
| |
| task doc: "doc:default" |
| task default: %i[compile test standard] |
| task purge: %i[clean clobber] |
| |
| desc "report gem version" |
| task :version do |
| print GEMSPEC.version |
| end |
| |
| # Rake's file task below writes into lib/, so model the directory explicitly. |
| directory "lib" |
| |
| extension_sources = FileList["Cargo.toml", "build.rs", "src/**/*.rs"] |
| # CargoBuilder invokes `cargo rustc --locked`, but Cargo.lock is not tracked |
| # for this binding. Model it as a generated file so Rake creates it only for |
| # tasks that need the native extension build. |
| file "Cargo.lock" => "Cargo.toml" do |
| sh ENV.fetch("CARGO", "cargo"), "generate-lockfile", "--manifest-path", "Cargo.toml" |
| end |
| |
| file EXTENSION_PATH => ["lib", "Cargo.lock", *extension_sources] do |
| original_verbose = Gem.configuration.verbose |
| # `:loud` enables RubyGems' highest verbosity and prints the exact Cargo |
| # command plus compiler output. That context is helpful when native CI fails. |
| Gem.configuration.verbose = :loud |
| Gem::Ext::CargoBuilder.new.build("Cargo.toml", ".", [], [], "lib", __dir__) |
| FileUtils.rm_f EXTENSION_FILE |
| ensure |
| Gem.configuration.verbose = original_verbose |
| end |
| |
| # Make `rake clean` remove the generated native extension. |
| CLEAN.include EXTENSION_PATH |
| |
| desc "Compile #{EXTENSION}" |
| task "compile:#{EXTENSION}" => EXTENSION_PATH |
| |
| desc "Compile all the extensions" |
| task compile: "compile:#{EXTENSION}" |
| |
| Rake::Task[:test].prerequisites << EXTENSION_PATH |
| |
| namespace :native do |
| desc "Build a native gem for #{ENV.fetch("OPENDAL_RUBY_PLATFORM", Gem::Platform.local.to_s)}" |
| task PACKAGE_NAME do |
| platform = ENV.fetch("OPENDAL_RUBY_PLATFORM", Gem::Platform.local.to_s) |
| Rake::Task["native:#{PACKAGE_NAME}:#{platform}"].invoke |
| end |
| |
| task "#{PACKAGE_NAME}:#{ENV.fetch("OPENDAL_RUBY_PLATFORM", Gem::Platform.local.to_s)}" => EXTENSION_PATH do |
| platform = ENV.fetch("OPENDAL_RUBY_PLATFORM", Gem::Platform.local.to_s) |
| spec = GEMSPEC.dup |
| spec.platform = Gem::Platform.new(platform) |
| spec.extensions.clear |
| spec.files |= [EXTENSION_PATH] |
| |
| FileUtils.mkdir_p "pkg" |
| gem_file = Gem::Package.build(spec) |
| FileUtils.mv gem_file, "pkg/#{File.basename(gem_file)}" |
| end |
| end |
| |
| task native: "native:#{PACKAGE_NAME}" |
| |
| # Ensure core files are copied before compilation and packaging |
| Rake::Task["build"].enhance(["copy_core"]) if Rake::Task.task_defined?("build") |
| |
| Rake::Task["release"].clear # clear the existing release task to allow override |
| Rake::Task["release:rubygem_push"].clear if Rake::Task.task_defined?("release:rubygem_push") |
| |
| # overrides bundler's default rake release task |
| # we removed build and git tagging steps. Read more in `./.github/workflows/release-ruby.yml` |
| # read more https://github.com/rubygems/rubygems/blob/master/bundler/lib/bundler/gem_helper.rb |
| desc "Multi-arch release" |
| task release: [ |
| "release:guard_clean", |
| "release:rubygem_push" |
| ] |
| |
| namespace :release do |
| desc "Push all gems to RubyGems" |
| task :rubygem_push do |
| # Push all gem files (source + platform-specific) like Nokogiri does |
| # Bundler's built_gem_path only returns the last modified gem, so we glob all gems instead |
| gem_files = Gem::Util.glob_files_in_dir("#{GEMSPEC.name}-*.gem", "pkg").sort |
| |
| if gem_files.empty? |
| abort "No gem files found in pkg/" |
| end |
| |
| puts "Found #{gem_files.length} gem(s) to push:" |
| gem_files.each { |f| puts " - #{File.basename(f)}" } |
| |
| gem_files.each do |gem_file| |
| puts "\nPushing #{File.basename(gem_file)}..." |
| sh "gem push #{gem_file}" |
| end |
| end |
| end |