diff --git a/.github/workflows/release_ruby.yml b/.github/workflows/release_ruby.yml index baa57cd5d9a3..b052ef89393f 100644 --- a/.github/workflows/release_ruby.yml +++ b/.github/workflows/release_ruby.yml @@ -98,6 +98,9 @@ jobs: - os: ubuntu-24.04-arm platform: aarch64-linux rust_target: aarch64-unknown-linux-gnu + - os: macos-14 + platform: arm64-darwin23 + rust_target: aarch64-apple-darwin - os: macos-15 platform: arm64-darwin24 rust_target: aarch64-apple-darwin @@ -109,14 +112,14 @@ jobs: continue-on-error: true env: - RB_SYS_CARGO_TARGET: ${{ matrix.rust_target }} + CARGO_BUILD_TARGET: ${{ matrix.rust_target }} + OPENDAL_RUBY_PLATFORM: ${{ matrix.platform }} steps: - name: Checkout repository uses: actions/checkout@v6 - name: Setup Rust toolchain - shell: bash run: | rustup toolchain install stable --profile minimal rustup default stable @@ -126,13 +129,13 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true - working-directory: bindings/ruby # must repeat because GitHub Actions will not use defaults.run + working-directory: bindings/ruby # must repeat because actions will not use defaults.run - name: Show available rake tasks run: bundle exec rake --tasks - name: Build native gem - run: bundle exec rake native:opendal:${{ matrix.platform }} gem + run: bundle exec rake native:opendal:${{ matrix.platform }} - name: Collect built gem run: | diff --git a/bindings/ruby/Cargo.toml b/bindings/ruby/Cargo.toml index bd6f4dd94f75..ae662deefa6e 100644 --- a/bindings/ruby/Cargo.toml +++ b/bindings/ruby/Cargo.toml @@ -25,7 +25,7 @@ homepage = "https://opendal.apache.org/" license = "Apache-2.0" repository = "https://github.com/apache/opendal" rust-version = "1.85" -version = "0.1.6" +version = "0.1.7-rc.1" [lib] crate-type = ["cdylib"] diff --git a/bindings/ruby/Gemfile b/bindings/ruby/Gemfile index f4e98957fd6f..734f8b16e4f5 100644 --- a/bindings/ruby/Gemfile +++ b/bindings/ruby/Gemfile @@ -24,8 +24,6 @@ gemspec group :development, :test do gem "rake", ">= 13.2" - gem "rb_sys", "~> 0.9.128" # for Makefile generation in extconf.rb - gem "rake-compiler", "~> 1.2.9" # to build a debug build gem "minitest", "~> 6.0.6" # test library gem "minitest-reporters", "~> 1.7.1" # better test output gem "activesupport", "~> 8.0.5" # testing support diff --git a/bindings/ruby/Rakefile b/bindings/ruby/Rakefile index 5ff06790dd7e..d52c009331a9 100644 --- a/bindings/ruby/Rakefile +++ b/bindings/ruby/Rakefile @@ -18,12 +18,22 @@ # frozen_string_literal: true require "bundler/gem_tasks" +require "fileutils" require "rake/testtask" -require "rb_sys/extensiontask" +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 @@ -52,29 +62,6 @@ task :copy_core do end end -RbSys::ExtensionTask.new(CRATE_PACKAGE_NAME, GEMSPEC) do |ext| - ext.name = "opendal_ruby" - ext.ext_dir = "." - ext.lib_dir = "lib/opendal_ruby" - - ext.cross_compile = true - ext.cross_platform = %w[ - x86_64-linux - arm64-linux - arm64-darwin - arm64-darwin23 - ] - - # Override Ruby version requirement for native gems - # This prevents automatic constraint to the build Ruby version (e.g., >= 3.3, < 3.4.dev) - ext.cross_compiling do |gem_spec| - # keep in sync with opendal.gemspec - gem_spec.required_ruby_version = ">= 3.2", "< 3.5.dev" - end -end - -Rake::Task[:test].prerequisites << :compile - Rake::TestTask.new("test:base") do |t| t.libs << "lib" t.libs << "test" @@ -132,6 +119,51 @@ task :version do print GEMSPEC.version end +# Rake's file task below writes into lib/, so model the directory explicitly. +directory "lib" + +file EXTENSION_PATH => FileList["Cargo.toml", "Cargo.lock", "build.rs", "src/**/*.rs"] do + original_verbose = Gem.configuration.verbose + Gem.configuration.verbose = true + 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") @@ -139,7 +171,7 @@ 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` +# 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: [ diff --git a/bindings/ruby/extconf.rb b/bindings/ruby/extconf.rb deleted file mode 100644 index 7b6e02c41198..000000000000 --- a/bindings/ruby/extconf.rb +++ /dev/null @@ -1,24 +0,0 @@ -# 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. - -require "mkmf" -# We use rb_sys for makefile generation only. -# We can use `RB_SYS_CARGO_PROFILE` to choose Cargo profile -# Read more https://github.com/oxidize-rb/rb-sys/blob/main/gem/README.md -require "rb_sys/mkmf" - -create_rust_makefile("opendal_ruby/opendal_ruby") diff --git a/bindings/ruby/lib/opendal.rb b/bindings/ruby/lib/opendal.rb index 78604e0eabdf..0b92d046157f 100644 --- a/bindings/ruby/lib/opendal.rb +++ b/bindings/ruby/lib/opendal.rb @@ -17,7 +17,7 @@ # frozen_string_literal: true -require_relative "opendal_ruby/opendal_ruby" +require_relative "opendal_ruby" require_relative "opendal_ruby/io" require_relative "opendal_ruby/entry" require_relative "opendal_ruby/metadata" diff --git a/bindings/ruby/opendal.gemspec b/bindings/ruby/opendal.gemspec index bb18e7c30cee..753f24fc3e53 100644 --- a/bindings/ruby/opendal.gemspec +++ b/bindings/ruby/opendal.gemspec @@ -17,7 +17,19 @@ # frozen_string_literal: true -require "json" +tracked_files_or_glob = lambda do |dir| + git_dir = File.join(dir, ".git") + + if File.exist?(git_dir) + IO.popen(["git", "-C", dir, "ls-files", "-z"], &:read).split("\x0") + else + Dir.chdir(dir) do + Dir.glob("**/*", File::FNM_DOTMATCH).reject do |f| + File.directory?(f) + end + end + end +end Gem::Specification.new do |spec| spec.name = "opendal" @@ -28,13 +40,10 @@ Gem::Specification.new do |spec| # OpenDAL relies on "version" in `Cargo.toml` for the release process. You can read this gem spec with: # `bundle exec ruby -e 'puts Gem::Specification.load("opendal.gemspec")'` # - # keep in sync the key "opendal-ruby" with `Rakefile`. - # - # uses `cargo` to extract the version. - spec.version = JSON.parse(`cargo metadata --format-version 1`.strip) - .fetch("packages") - .find { |p| p["name"] == "opendal-ruby" } - .fetch("version") + # Read from Cargo.toml directly so Bundler can evaluate this gemspec from an + # unpacked native gem without requiring the Rust workspace or network access. + cargo_toml = File.read(File.join(__dir__, "Cargo.toml")) + spec.version = cargo_toml.match(/^\s*version\s*=\s*"([^"]+)"/)[1] spec.authors = ["OpenDAL Contributors"] spec.email = ["dev@opendal.apache.org"] @@ -52,24 +61,21 @@ Gem::Specification.new do |spec| } # Specify which files should be added to a source release gem when we release OpenDAL Ruby gem. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + # Prefer git-tracked files while building from a checkout, and fall back to + # the unpacked files when Bundler loads this gemspec from an installed gem. spec.files = Dir.chdir(__dir__) do - git_files = `git ls-files -z`.split("\x0").reject do |f| + git_files = tracked_files_or_glob.call(__dir__).reject do |f| f.start_with?(*%w[gems/ pkg/ target/ tmp/ .git]) end # When building release package, include core directory files for rake build - core_dir = "../../core" distributed_core_dir = "core" if Dir.exist?(distributed_core_dir) # Core files should already be copied by the Rakefile's copy_core task - core_files = `git -C #{File.expand_path(core_dir, __dir__)} ls-files -z` - .split("\x0") - .filter_map do |f| - full_path = "#{distributed_core_dir}/#{f}" - full_path if File.exist?(full_path) - end + core_files = tracked_files_or_glob.call(File.expand_path(distributed_core_dir, __dir__)).map do |f| + "#{distributed_core_dir}/#{f}" + end git_files + core_files else @@ -79,7 +85,10 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] - spec.extensions = ["./extconf.rb"] + native_extension = Dir.glob(File.join(__dir__, "lib", "opendal_ruby.*")).any? do |path| + File.file?(path) && File.extname(path) != ".rb" + end + spec.extensions = native_extension ? [] : ["./Cargo.toml"] # Exclude non-Ruby files from RDoc to prevent parsing errors spec.rdoc_options = ["--exclude", "Cargo\\..*", "--exclude", "core/", "--exclude", "\\.rs$"]