From be4651604c360b43bd802a8dadaa4bd2429a1e31 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 4 Jun 2026 18:12:37 +0100 Subject: [PATCH 01/12] Fix release workflow for rb-sys mkmf build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native-gem cross-compile step used gem-compiler, which unpacks the source gem and runs its extconf.rb in isolation — without rb_sys on the load path. After the move to the rb-sys mkmf build, this fails with `cannot load such file -- rb_sys/mkmf`. Drop the precompiled-native-gem matrix and publish the source gem only. The native extension is compiled at install time, so no functionality is lost. --- .github/workflows/release.yml | 105 +++------------------------------- 1 file changed, 9 insertions(+), 96 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 034c9da..37b0f32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,111 +5,24 @@ on: - "[0-9]*.[0-9]*.[0-9]*" jobs: - build_gem: - name: Build source gem + release: + name: Build source gem + create release runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby-pkgs@v1 with: - ruby-version: 3.4 - - run: sudo apt-get update && sudo apt-get install -y libopencv-dev libvips + ruby-version: "3.4" - run: gem update --system - run: rake gem - - name: Upload source gem - uses: actions/upload-artifact@v4 - with: - name: libfacedetection.gem - path: pkg/*.gem - retention-days: 1 - - compile_native_gems: - name: Compile native gem - needs: build_gem - strategy: - matrix: - include: - - os: ubuntu-24.04 - platform: x86_64-linux - ruby: "3.4" - - os: ubuntu-24.04-arm - platform: aarch64-linux - ruby: "3.4" - - os: macos-latest - platform: arm64-darwin - ruby: "3.4" - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - - uses: ruby/setup-ruby-pkgs@v1 - with: - ruby-version: ${{ matrix.ruby }} - - - name: Install system dependencies on Linux - if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-latest-arm64' - run: sudo apt-get update && sudo apt-get install -y libopencv-dev libvips - - - name: Install system dependencies on macOS - if: matrix.os == 'macos-latest' - run: brew install opencv vips - - - name: Install gem-compiler - run: gem install gem-compiler - - - name: Download source gem - uses: actions/download-artifact@v4 - with: - name: libfacedetection.gem - path: pkg/ - - - name: Compile gem - run: | - SOURCE_GEM=$(ls pkg/*.gem | grep -v -- '-x86_64-linux\|aarch64-linux\|arm64-darwin') - gem compile $SOURCE_GEM --prune - - - name: Upload compiled gem - uses: actions/upload-artifact@v4 - with: - name: libfacedetection-${{ matrix.platform }}.gem - path: ./*.gem - retention-days: 1 - - release: - name: Create GitHub Release - needs: compile_native_gems - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Extract version - id: extract_version - run: | - VERSION=${GITHUB_REF#refs/tags/} - echo "GEM_VERSION=$VERSION" >> $GITHUB_ENV - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - - - name: Rename gem files with version - run: | - mkdir -p release_gems - for gem in artifacts/libfacedetection-*.gem/*.gem; do - platform=$(basename $gem | sed -E 's/libfacedetection-([^-]+-[^-]+)\.gem/\1/') - - # Construct the target filename: NAME-VERSION-PLATFORM.gem - target_filename="libfacedetection-${GEM_VERSION}-${platform}.gem" - target_path="release_gems/$target_filename" - mv "$gem" "$target_path" - done - # Move source gem to release_gems directory - mv artifacts/libfacedetection.gem/*.gem release_gems/libfacedetection-${GEM_VERSION}.gem - + run: echo "GEM_VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV" - name: Create release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ env.GEM_VERSION }} - files: release_gems/*.gem + tag_name: ${{ github.ref_name }} + name: libfacedetection ${{ env.GEM_VERSION }} + files: pkg/*.gem generate_release_notes: true From a8fb96ae37321d847d5d8c86577bbcaaffe7b07e Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 12:45:30 +0100 Subject: [PATCH 02/12] Cross-compile native gems via rake-compiler + rb-sys-dock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes the gem-compiler-based approach. gem-compiler unpacks the source gem and runs extconf.rb in isolation, without rb_sys on the load path; after the move to the rb-sys mkmf build, that step fails with `cannot load such file -- rb_sys/mkmf`. Replace it with the rake-compiler + rb-sys-dock pattern used by the other in-house rb-sys gems (distributing_iterator, gems/fetlife_markdown_renderer in fetlife-web): - Rakefile: switch from Gem::PackageTask + gem-compiler to RbSys::ExtensionTask + a dock:build:* namespace that shells out to rb-sys-dock. Cross-platforms: x86_64-linux, aarch64-linux, x86_64-darwin, arm64-darwin. - Gemfile: pin rake-compiler ~> 1.3 and rb_sys >= 0.9.126 for the cross-compile path. - .github/workflows/release.yml: three-job pipeline mirroring distributing-iterator — build_source_gem, build_native_gems (matrix per platform via `bundle exec rb-sys-dock`), release (attach all gems to the tag). The opencv cargo feature stays optional/off-by-default; cross-compiled gems ship libfacedetection (vendored C++, built from source via cc-rs) without opencv. Consumers who need opencv keep building from source. --- .github/workflows/release.yml | 69 ++++++++++++++++++++++++---- Gemfile | 3 ++ Rakefile | 85 +++++++++++++++++++++++++++-------- 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37b0f32..43f1abd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,26 +3,77 @@ on: push: tags: - "[0-9]*.[0-9]*.[0-9]*" + - "v[0-9]*.[0-9]*.[0-9]*" jobs: + build_source_gem: + name: Build source gem + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6.0.2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4 + bundler-cache: true + - run: bundle exec rake build + - name: Upload source gem + uses: actions/upload-artifact@v7.0.0 + with: + name: source-gem + path: pkg/*.gem + retention-days: 1 + + build_native_gems: + name: Build native gem (${{ matrix.platform }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - x86_64-linux + - aarch64-linux + - x86_64-darwin + - arm64-darwin + steps: + - uses: actions/checkout@v6.0.2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4 + bundler-cache: true + - name: Build native gems + run: bundle exec rb-sys-dock --platform ${{ matrix.platform }} --build + - name: Upload native gems + uses: actions/upload-artifact@v7.0.0 + with: + name: native-gem-${{ matrix.platform }} + path: pkg/*-${{ matrix.platform }}.gem + retention-days: 1 + release: - name: Build source gem + create release + name: Create GitHub Release + needs: + - build_source_gem + - build_native_gems runs-on: ubuntu-latest permissions: contents: write steps: - - uses: actions/checkout@v4 - - uses: ruby/setup-ruby-pkgs@v1 - with: - ruby-version: "3.4" - - run: gem update --system - - run: rake gem - name: Extract version - run: echo "GEM_VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_ENV" + id: extract_version + run: | + VERSION=${GITHUB_REF#refs/tags/} + VERSION=${VERSION#v} + echo "GEM_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Download all artifacts + uses: actions/download-artifact@v8.0.1 + with: + path: artifacts + - name: Create release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.ref_name }} name: libfacedetection ${{ env.GEM_VERSION }} - files: pkg/*.gem + files: artifacts/**/*.gem generate_release_notes: true diff --git a/Gemfile b/Gemfile index b4e2a20..5b5fa18 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ source "https://rubygems.org" gemspec + +gem "rake-compiler", "~> 1.3" +gem "rb_sys", ">= 0.9.126", "< 1.0" diff --git a/Rakefile b/Rakefile index a20b0b6..4a72cad 100644 --- a/Rakefile +++ b/Rakefile @@ -1,29 +1,78 @@ -require 'rubygems/package_task' +# frozen_string_literal: true -spec = eval(File.read("libfacedetection.gemspec")) -GEM_RUBY_VERSION = "3.4.0" -DOCKER_IMAGE = "ruby:#{GEM_RUBY_VERSION}-bullseye" +begin + require "bundler/setup" + require "bundler/gem_tasks" +rescue LoadError + # Allow plain Ruby environments to use already-installed gems. +end + +require "rb_sys/extensiontask" +require "shellwords" + +GEMSPEC = Gem::Specification.load(File.expand_path("libfacedetection.gemspec", __dir__)) +CROSS_PLATFORMS = %w[ + x86_64-linux + aarch64-linux + x86_64-darwin + arm64-darwin +].freeze -def compile_cmd(_arch) - "gem instal gem-compiler; curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; source '/root/.cargo/env'; rake gem:native" +RbSys::ExtensionTask.new("libfacedetection", GEMSPEC) do |ext| + ext.lib_dir = "lib/libfacedetection" + ext.cross_compile = true + requested_platforms = [ENV["RUBY_TARGET"]].compact + ext.cross_platform = requested_platforms.empty? ? CROSS_PLATFORMS : requested_platforms end -gem_task = Gem::PackageTask.new(spec) do |pkg| - pkg.need_zip = true - pkg.need_tar = true +def normalize_platforms(value) + Array(value) + .flat_map { |entry| entry.to_s.split(",") } + .map(&:strip) + .reject(&:empty?) end -desc "Generate a pre-compiled native gem for #{RUBY_PLATFORM}" -task "gem:native" => ["gem"] do - sh "gem compile #{gem_task.package_dir_path}.gem" +def resolve_platforms(args) + requested = normalize_platforms(args.to_a + [ENV["PLATFORMS"], ENV["TARGET"]]) + platforms = requested.empty? ? CROSS_PLATFORMS : requested + invalid_platforms = platforms - CROSS_PLATFORMS + + return platforms if invalid_platforms.empty? + + abort "Unsupported platform(s): #{invalid_platforms.join(', ')}. Supported: #{CROSS_PLATFORMS.join(', ')}" end -desc "Generate a pre-compiled native gem for aarch64-linux" -task "gem:native:aarch64-linux" => ["gem"] do - sh %{docker run --rm --platform linux/arm64 -v $(pwd):/src -w /src #{DOCKER_IMAGE} /bin/bash -c "#{compile_cmd('aarch64-unknown-linux-musl')}"} +def run_rb_sys_dock!(platform) + command = ["rb-sys-dock", "--platform", platform, "--build"] + + puts "==> #{command.shelljoin}" + sh(*command) + return if $?.success? + + abort "rb-sys-dock build failed for #{platform}" end -desc "Generate a pre-compiled native gem for x86_64-linux" -task "gem:native:x86_64-linux" => ["gem"] do - sh %{docker run --rm --platform linux/amd64 -v $(pwd):/src -w /src #{DOCKER_IMAGE} /bin/bash -c "#{compile_cmd('x86_64-unknown-linux-musl')}"} +namespace :dock do + namespace :build do + desc "Cross-compile the native gem for all configured platforms via rb-sys-dock" + task :all do + CROSS_PLATFORMS.each { |platform| run_rb_sys_dock!(platform) } + end + + desc "Cross-compile the native gem via rb-sys-dock. Use TARGET=platform or PLATFORMS=platform1,platform2" + task :selected, [:platform] do |_task, args| + resolve_platforms([args[:platform]]).each do |platform| + run_rb_sys_dock!(platform) + end + end + + CROSS_PLATFORMS.each do |platform| + task_name = platform.tr("-", "_").to_sym + + desc "Cross-compile the native gem for #{platform} via rb-sys-dock" + task task_name do + run_rb_sys_dock!(platform) + end + end + end end From 51d4a561a060bf4d66f791c1ee4d1ede0850992d Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 12:49:49 +0100 Subject: [PATCH 03/12] Fix run-tests CI: bundle install before rake build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new Rakefile requires `bundler/setup` so it can use bundler-managed deps (rake-compiler, rb_sys) for the cross-compile path. The old run-tests workflow ran `rake gem` before `bundle install`, which now aborts with `Bundler::GemNotFound`. Modernize to match distributing-iterator/ci.yml: - ruby/setup-ruby@v1 with bundler-cache: true (auto-runs bundle install) - actions-rust-lang/setup-rust-toolchain@v1 for the native compile - bundle exec rake build (replaces rake gem) - Drop libopencv-dev — the default cargo feature set doesn't pull opencv, and the smoke test only uses ruby-vips - Drop the long commented-out compile_native_gem block; the real cross-compile lives in release.yml now --- .github/workflows/workflow.yml | 52 ++++++---------------------------- 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d0b074a..18af0be 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -5,49 +5,13 @@ jobs: name: Build and test ruby extension runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby-pkgs@v1 + - uses: actions/checkout@v6.0.2 + - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.4 - - run: sudo apt-get update && sudo apt-get install -y libopencv-dev libvips - - run: gem update --system - - run: rake gem - - run: gem install $(ls -1 pkg/*.gem) - - run: bundle install + ruby-version: "3.4" + bundler-cache: true + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: sudo apt-get update && sudo apt-get install -y libvips + - run: bundle exec rake build + - run: gem install pkg/libfacedetection-*.gem - run: ruby tests/test_detection.rb - # compile_native_gem: - # name: Compile native gem - # runs-on: ubuntu-latest - # strategy: - # matrix: - # platform: - # - x86_64-linux - # - x86_64-darwin - # - arm64-darwin - # - aarch64-linux - # steps: - # - uses: actions/checkout@v2 - - # - uses: ruby/setup-ruby@v1 - # with: - # ruby-version: '3.1' - # bundler-cache: true - - # - uses: oxidize-rb/cross-gem-action@main - # with: - # platform: ${{ matrix.platform }} - # setup: | # optional - # env - # bundle install - # env: | # optional - # RUBY_CC_VERSION=3.1.0:3.0.0:2.7.0 - # CXX_aarch64_unknown_linux_gnu= - - # - uses: actions/download-artifact@v3 - # with: - # name: cross-gem - # path: pkg/ - - # - name: Display structure of built gems - # run: ls -R - # working-directory: pkg/ From bbb65977d8dde356a7703a22a97c2591b23c50c3 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 12:54:39 +0100 Subject: [PATCH 04/12] Align cargo package name with gem name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RbSys::ExtensionTask.new("libfacedetection", ...) looks up cargo package metadata by exact name match against `[package].name`. The crate was named `libfacedetection-ruby` (chosen in the original rb-sys migration to differentiate from the upstream `libfacedetection-rs` git dep), so rake build aborts with `RbSys::PackageNotFoundError`. Rename our package to match the gem name and alias the upstream dep under a non-conflicting name: - ext/libfacedetection/Cargo.toml: - [package].name: libfacedetection-ruby -> libfacedetection - [lib].name: libfacedetection_ruby -> libfacedetection - dep alias: libfacedetection_rs (package = "libfacedetection") - default feature: libfacedetection_rs (was libfacedetection) - src/lib.rs: libfacedetection::facedetect_cnn -> libfacedetection_rs::, feature gates updated. Ruby-visible Symbol "libfacedetection" kept as the public feature label. - libfacedetection.gemspec: cargo_crate_name metadata -> libfacedetection - Cargo.lock: regenerated extconf.rb / install-time bundle install path is unaffected — it just runs `cargo build` against whatever is in the manifest. --- Cargo.lock | 20 ++++++++++---------- ext/libfacedetection/Cargo.toml | 8 ++++---- ext/libfacedetection/src/lib.rs | 10 +++++----- libfacedetection.gemspec | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0e5866..a0d69da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,23 +265,23 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libfacedetection" -version = "0.1.0" -source = "git+https://github.com/fetlife/libfacedetection-rs.git?rev=7426f33ba101514932a5ef58456761735c7bf3dc#7426f33ba101514932a5ef58456761735c7bf3dc" -dependencies = [ - "libfacedetection-sys", - "thiserror", -] - -[[package]] -name = "libfacedetection-ruby" version = "0.0.1" dependencies = [ - "libfacedetection", + "libfacedetection 0.1.0", "magnus", "opencv", "rb-sys", ] +[[package]] +name = "libfacedetection" +version = "0.1.0" +source = "git+https://github.com/fetlife/libfacedetection-rs.git?rev=7426f33ba101514932a5ef58456761735c7bf3dc#7426f33ba101514932a5ef58456761735c7bf3dc" +dependencies = [ + "libfacedetection-sys", + "thiserror", +] + [[package]] name = "libfacedetection-sys" version = "0.2.1" diff --git a/ext/libfacedetection/Cargo.toml b/ext/libfacedetection/Cargo.toml index 814777a..871fbc6 100644 --- a/ext/libfacedetection/Cargo.toml +++ b/ext/libfacedetection/Cargo.toml @@ -1,19 +1,19 @@ [package] -name = "libfacedetection-ruby" +name = "libfacedetection" version = "0.0.1" edition = "2021" publish = false authors = ["Fetlife "] [lib] -name = "libfacedetection_ruby" +name = "libfacedetection" crate-type = ["cdylib"] [dependencies] -libfacedetection = { git = "https://github.com/fetlife/libfacedetection-rs.git", optional = true, rev = "7426f33ba101514932a5ef58456761735c7bf3dc" } +libfacedetection_rs = { package = "libfacedetection", git = "https://github.com/fetlife/libfacedetection-rs.git", optional = true, rev = "7426f33ba101514932a5ef58456761735c7bf3dc" } opencv = { version = "0.88.6", optional = true, features = ["clang-runtime"] } magnus = { version = "0.8.2" } rb-sys = { version = "0.9.126", default-features = false, features = ["stable-api-compiled-fallback"] } [features] -default = ["libfacedetection"] +default = ["libfacedetection_rs"] diff --git a/ext/libfacedetection/src/lib.rs b/ext/libfacedetection/src/lib.rs index 5c9552e..df0e358 100644 --- a/ext/libfacedetection/src/lib.rs +++ b/ext/libfacedetection/src/lib.rs @@ -51,7 +51,7 @@ fn detect_opencv(content: Vec) -> Result { Ok(result) } -#[cfg(all(feature = "libfacedetection", feature = "opencv"))] +#[cfg(all(feature = "libfacedetection_rs", feature = "opencv"))] fn detect_libfacedetection(content: Vec) -> Result { use opencv::{imgcodecs, prelude::*, types}; @@ -83,7 +83,7 @@ fn detect_opencv(_content: Vec) -> Result { )) } -#[cfg(not(all(feature = "libfacedetection", feature = "opencv")))] +#[cfg(not(all(feature = "libfacedetection_rs", feature = "opencv")))] fn detect_libfacedetection(_content: Vec) -> Result { Err(Error::new( exception::runtime_error(), @@ -91,14 +91,14 @@ fn detect_libfacedetection(_content: Vec) -> Result { )) } -#[cfg(feature = "libfacedetection")] +#[cfg(feature = "libfacedetection_rs")] fn detect_libfacedetection_data( bgrdata: *const u8, width: i32, height: i32, step: Option, ) -> Result { - let facedetect_result = libfacedetection::facedetect_cnn( + let facedetect_result = libfacedetection_rs::facedetect_cnn( bgrdata, width, height, @@ -152,7 +152,7 @@ fn pub_detect_libfacedetection_image_data( fn features() -> Result { let result = RArray::new(); - #[cfg(feature = "libfacedetection")] + #[cfg(feature = "libfacedetection_rs")] { result.push(magnus::Symbol::new("libfacedetection"))?; } diff --git a/libfacedetection.gemspec b/libfacedetection.gemspec index 8f90adc..bc86716 100644 --- a/libfacedetection.gemspec +++ b/libfacedetection.gemspec @@ -39,6 +39,6 @@ Gem::Specification.new do |spec| spec.metadata = { "github_repo" => "ssh://github.com/fetlife/facedetection-ruby", - "cargo_crate_name" => "libfacedetection-ruby" + "cargo_crate_name" => "libfacedetection" } end From 242a0f9df3a024004a570c397511da8e2a6b4a39 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 12:57:14 +0100 Subject: [PATCH 05/12] Disable -D warnings in run-tests CI actions-rust-lang/setup-rust-toolchain@v1 defaults to exporting RUSTFLAGS=-D warnings, which turns the pre-existing magnus 0.8.2 deprecation warnings (Symbol::new, Integer::from_i64, RArray::new, exception::runtime_error) into hard compile errors at `gem install` time. Opt out via `rustflags: ""`. The deprecations are real tech debt to clean up later (every call site needs threading the Ruby handle through), but not in scope for fixing the release workflow. release.yml is unaffected: rb-sys-dock runs inside a docker container with its own Rust toolchain, not the runner's env. --- .github/workflows/workflow.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 18af0be..cbe4fdc 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -11,6 +11,8 @@ jobs: ruby-version: "3.4" bundler-cache: true - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + rustflags: "" - run: sudo apt-get update && sudo apt-get install -y libvips - run: bundle exec rake build - run: gem install pkg/libfacedetection-*.gem From 9ef7643e03e15b668dc44ac7827a860901a0222c Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 13:01:00 +0100 Subject: [PATCH 06/12] Align extconf.rb + loader with new lib name When cargo's [lib].name changed from libfacedetection_ruby to libfacedetection, the generated Makefile target name didn't follow: extconf.rb still asked rb_sys/mkmf to build "libfacedetection_ruby" as the artifact, so make tried to copy a non-existent target/release/liblibfacedetection_ruby.so and failed. - ext/libfacedetection/extconf.rb: create_rust_makefile target -> "libfacedetection/libfacedetection" - lib/libfacedetection.rb: load paths -> "libfacedetection" (no _ruby) Mirrors gems/fetlife_markdown_renderer's loader pattern where gem name = lib name = file name throughout. --- ext/libfacedetection/extconf.rb | 2 +- lib/libfacedetection.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/libfacedetection/extconf.rb b/ext/libfacedetection/extconf.rb index a4f2855..9d3d56c 100644 --- a/ext/libfacedetection/extconf.rb +++ b/ext/libfacedetection/extconf.rb @@ -1,4 +1,4 @@ require "mkmf" require "rb_sys/mkmf" -create_rust_makefile("libfacedetection/libfacedetection_ruby") +create_rust_makefile("libfacedetection/libfacedetection") diff --git a/lib/libfacedetection.rb b/lib/libfacedetection.rb index 9ffe5f3..324bbab 100644 --- a/lib/libfacedetection.rb +++ b/lib/libfacedetection.rb @@ -14,9 +14,9 @@ def available? begin [ - "libfacedetection/#{Libfacedetection::RUBY_API_VERSION}/libfacedetection_ruby", - "libfacedetection/libfacedetection_ruby", - "../ext/libfacedetection/libfacedetection_ruby" + "libfacedetection/#{Libfacedetection::RUBY_API_VERSION}/libfacedetection", + "libfacedetection/libfacedetection", + "../ext/libfacedetection/libfacedetection" ].each do |path| begin require_relative path From ee9d400149b769e4ec19ec3e0d76d9279a7e60b6 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 13:03:03 +0100 Subject: [PATCH 07/12] Run smoke test via bundler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ruby tests/test_detection.rb` ran in system Ruby and couldn't find ruby-vips, which is bundler-managed (vendor/bundle/, not system gems). Switch to: - `bundle exec rake compile` — builds the native extension into lib/libfacedetection/ in-place (loader picks it up via relative require). - `bundle exec ruby tests/test_detection.rb` — bundler activates libfacedetection from the local gemspec source plus ruby-vips/ffi dev deps. Add minitest to the Gemfile (test group) — the smoke test uses minitest/autorun but it wasn't listed anywhere, so it only worked when system Ruby happened to ship it as a bundled gem. --- .github/workflows/workflow.yml | 4 ++-- Gemfile | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index cbe4fdc..4582053 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -15,5 +15,5 @@ jobs: rustflags: "" - run: sudo apt-get update && sudo apt-get install -y libvips - run: bundle exec rake build - - run: gem install pkg/libfacedetection-*.gem - - run: ruby tests/test_detection.rb + - run: bundle exec rake compile + - run: bundle exec ruby tests/test_detection.rb diff --git a/Gemfile b/Gemfile index 5b5fa18..0141dd4 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,7 @@ gemspec gem "rake-compiler", "~> 1.3" gem "rb_sys", ">= 0.9.126", "< 1.0" + +group :test do + gem "minitest", "~> 5.0" +end From 3078a93495612ae3434fa0ab03d36f52109a2039 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 13:22:28 +0100 Subject: [PATCH 08/12] Align with distributing-iterator's newer pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rakefile: drop the dock:build:* namespace + CROSS_PLATFORMS + the resolve_platforms helpers. Cross-compile is invoked directly via `bundle exec rb-sys-dock --platform X --build` from release.yml, so the rake tasks were dead weight. Add `Rake::TestTask` so `rake test` compiles via the `compile:dev` dep and runs the smoke test in one step. Drop the eager `require "bundler/setup"` — `bundler/gem_tasks` already pulls bundler in. - Gemfile: revert to bare `source + gemspec`. All dev pins live in the gemspec (single-source-of-truth, matches distributing-iterator). - libfacedetection.gemspec: - move minitest "~> 5.0" from Gemfile group :test - loosen rb_sys from "~> 0.9.126" to "~> 0.9" (matches distributing-iterator; lets future 0.9.x patches in) - extconf.rb: add `config.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", "release").to_sym` so debug builds are toggleable via env (QoL). - workflow.yml: replace the explicit build/compile/test trio with a single `bundle exec rake test`. --- .github/workflows/workflow.yml | 4 +- Gemfile | 7 --- Rakefile | 79 +++++---------------------------- ext/libfacedetection/extconf.rb | 4 +- libfacedetection.gemspec | 3 +- 5 files changed, 17 insertions(+), 80 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 4582053..1d3f7fa 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,6 +14,4 @@ jobs: with: rustflags: "" - run: sudo apt-get update && sudo apt-get install -y libvips - - run: bundle exec rake build - - run: bundle exec rake compile - - run: bundle exec ruby tests/test_detection.rb + - run: bundle exec rake test diff --git a/Gemfile b/Gemfile index 0141dd4..b4e2a20 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,3 @@ source "https://rubygems.org" gemspec - -gem "rake-compiler", "~> 1.3" -gem "rb_sys", ">= 0.9.126", "< 1.0" - -group :test do - gem "minitest", "~> 5.0" -end diff --git a/Rakefile b/Rakefile index 4a72cad..836b342 100644 --- a/Rakefile +++ b/Rakefile @@ -1,78 +1,21 @@ # frozen_string_literal: true -begin - require "bundler/setup" - require "bundler/gem_tasks" -rescue LoadError - # Allow plain Ruby environments to use already-installed gems. -end - +require "bundler/gem_tasks" +require "rake/testtask" require "rb_sys/extensiontask" -require "shellwords" -GEMSPEC = Gem::Specification.load(File.expand_path("libfacedetection.gemspec", __dir__)) -CROSS_PLATFORMS = %w[ - x86_64-linux - aarch64-linux - x86_64-darwin - arm64-darwin -].freeze +GEMSPEC = Gem::Specification.load("libfacedetection.gemspec") + +task default: :test +task gem: :build RbSys::ExtensionTask.new("libfacedetection", GEMSPEC) do |ext| ext.lib_dir = "lib/libfacedetection" - ext.cross_compile = true - requested_platforms = [ENV["RUBY_TARGET"]].compact - ext.cross_platform = requested_platforms.empty? ? CROSS_PLATFORMS : requested_platforms -end - -def normalize_platforms(value) - Array(value) - .flat_map { |entry| entry.to_s.split(",") } - .map(&:strip) - .reject(&:empty?) -end - -def resolve_platforms(args) - requested = normalize_platforms(args.to_a + [ENV["PLATFORMS"], ENV["TARGET"]]) - platforms = requested.empty? ? CROSS_PLATFORMS : requested - invalid_platforms = platforms - CROSS_PLATFORMS - - return platforms if invalid_platforms.empty? - - abort "Unsupported platform(s): #{invalid_platforms.join(', ')}. Supported: #{CROSS_PLATFORMS.join(', ')}" -end - -def run_rb_sys_dock!(platform) - command = ["rb-sys-dock", "--platform", platform, "--build"] - - puts "==> #{command.shelljoin}" - sh(*command) - return if $?.success? - - abort "rb-sys-dock build failed for #{platform}" end -namespace :dock do - namespace :build do - desc "Cross-compile the native gem for all configured platforms via rb-sys-dock" - task :all do - CROSS_PLATFORMS.each { |platform| run_rb_sys_dock!(platform) } - end - - desc "Cross-compile the native gem via rb-sys-dock. Use TARGET=platform or PLATFORMS=platform1,platform2" - task :selected, [:platform] do |_task, args| - resolve_platforms([args[:platform]]).each do |platform| - run_rb_sys_dock!(platform) - end - end - - CROSS_PLATFORMS.each do |platform| - task_name = platform.tr("-", "_").to_sym - - desc "Cross-compile the native gem for #{platform} via rb-sys-dock" - task task_name do - run_rb_sys_dock!(platform) - end - end - end +Rake::TestTask.new do |t| + t.deps << "compile:dev" + t.libs << "lib" + t.libs << "tests" + t.test_files = FileList["tests/test_*.rb"] end diff --git a/ext/libfacedetection/extconf.rb b/ext/libfacedetection/extconf.rb index 9d3d56c..4b0d081 100644 --- a/ext/libfacedetection/extconf.rb +++ b/ext/libfacedetection/extconf.rb @@ -1,4 +1,6 @@ require "mkmf" require "rb_sys/mkmf" -create_rust_makefile("libfacedetection/libfacedetection") +create_rust_makefile("libfacedetection/libfacedetection") do |config| + config.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", "release").to_sym +end diff --git a/libfacedetection.gemspec b/libfacedetection.gemspec index bc86716..92d8adc 100644 --- a/libfacedetection.gemspec +++ b/libfacedetection.gemspec @@ -31,9 +31,10 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.extensions = ["ext/libfacedetection/extconf.rb"] - spec.add_dependency "rb_sys", "~> 0.9.126" + spec.add_dependency "rb_sys", "~> 0.9" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rake-compiler", "~> 1.2" + spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "ruby-vips" spec.add_development_dependency "ffi" From 034f2da3657b701ba020b4f62293b16fd3bba182 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 13:22:36 +0100 Subject: [PATCH 09/12] Bump version to 0.4.1 --- lib/libfacedetection/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libfacedetection/version.rb b/lib/libfacedetection/version.rb index bea9de5..df49c74 100644 --- a/lib/libfacedetection/version.rb +++ b/lib/libfacedetection/version.rb @@ -1,3 +1,3 @@ module Libfacedetection - VERSION = "0.4.0".freeze + VERSION = "0.4.1".freeze end From 871da36b39a27389f9f5421740ed895de553371e Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 14:02:17 +0100 Subject: [PATCH 10/12] Finish libfacedetection extension rename --- .github/workflows/release.yml | 2 +- .github/workflows/workflow.yml | 28 ++++++++++++++++++++++------ Readme.md => README.md | 2 +- ext/libfacedetection/Cargo.toml | 3 ++- ext/libfacedetection/extconf.rb | 2 ++ ext/libfacedetection/src/lib.rs | 8 ++++---- libfacedetection.gemspec | 2 +- 7 files changed, 33 insertions(+), 14 deletions(-) rename Readme.md => README.md (96%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43f1abd..07a937c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,7 +63,7 @@ jobs: run: | VERSION=${GITHUB_REF#refs/tags/} VERSION=${VERSION#v} - echo "GEM_VERSION=$VERSION" >> $GITHUB_ENV + echo "GEM_VERSION=$VERSION" >> "$GITHUB_ENV" - name: Download all artifacts uses: actions/download-artifact@v8.0.1 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1d3f7fa..1b02c5d 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,17 +1,33 @@ name: run-tests -on: [push] + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + jobs: - build_gem: - name: Build and test ruby extension - runs-on: ubuntu-latest + ruby_extension: + name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + ruby: ["3.2", "3.3", "3.4", "4.0"] steps: - uses: actions/checkout@v6.0.2 - uses: ruby/setup-ruby@v1 with: - ruby-version: "3.4" + ruby-version: ${{ matrix.ruby }} bundler-cache: true - uses: actions-rust-lang/setup-rust-toolchain@v1 with: rustflags: "" - - run: sudo apt-get update && sudo apt-get install -y libvips + - name: Install libvips on Linux + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y libvips + - name: Install libvips on macOS + if: runner.os == 'macOS' + run: brew install vips - run: bundle exec rake test diff --git a/Readme.md b/README.md similarity index 96% rename from Readme.md rename to README.md index 0093a7b..53aa907 100644 --- a/Readme.md +++ b/README.md @@ -10,6 +10,7 @@ This gem is automatically released using Github Releases when a new version tag 2. Compiling native extensions for multiple platforms: - x86_64-linux (Ubuntu 24.04) - aarch64-linux (Ubuntu 24.04 ARM) + - x86_64-darwin (macOS) - arm64-darwin (macOS) To create a new release: @@ -22,4 +23,3 @@ To create a new release: ``` You can find all releases on the [GitHub Releases page](https://github.com/fetlife/facedetection-ruby/releases). - diff --git a/ext/libfacedetection/Cargo.toml b/ext/libfacedetection/Cargo.toml index 871fbc6..8460d84 100644 --- a/ext/libfacedetection/Cargo.toml +++ b/ext/libfacedetection/Cargo.toml @@ -16,4 +16,5 @@ magnus = { version = "0.8.2" } rb-sys = { version = "0.9.126", default-features = false, features = ["stable-api-compiled-fallback"] } [features] -default = ["libfacedetection_rs"] +default = ["libfacedetection"] +libfacedetection = ["dep:libfacedetection_rs"] diff --git a/ext/libfacedetection/extconf.rb b/ext/libfacedetection/extconf.rb index 4b0d081..45d3f0d 100644 --- a/ext/libfacedetection/extconf.rb +++ b/ext/libfacedetection/extconf.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "mkmf" require "rb_sys/mkmf" diff --git a/ext/libfacedetection/src/lib.rs b/ext/libfacedetection/src/lib.rs index df0e358..567b9af 100644 --- a/ext/libfacedetection/src/lib.rs +++ b/ext/libfacedetection/src/lib.rs @@ -51,7 +51,7 @@ fn detect_opencv(content: Vec) -> Result { Ok(result) } -#[cfg(all(feature = "libfacedetection_rs", feature = "opencv"))] +#[cfg(all(feature = "libfacedetection", feature = "opencv"))] fn detect_libfacedetection(content: Vec) -> Result { use opencv::{imgcodecs, prelude::*, types}; @@ -83,7 +83,7 @@ fn detect_opencv(_content: Vec) -> Result { )) } -#[cfg(not(all(feature = "libfacedetection_rs", feature = "opencv")))] +#[cfg(not(all(feature = "libfacedetection", feature = "opencv")))] fn detect_libfacedetection(_content: Vec) -> Result { Err(Error::new( exception::runtime_error(), @@ -91,7 +91,7 @@ fn detect_libfacedetection(_content: Vec) -> Result { )) } -#[cfg(feature = "libfacedetection_rs")] +#[cfg(feature = "libfacedetection")] fn detect_libfacedetection_data( bgrdata: *const u8, width: i32, @@ -152,7 +152,7 @@ fn pub_detect_libfacedetection_image_data( fn features() -> Result { let result = RArray::new(); - #[cfg(feature = "libfacedetection_rs")] + #[cfg(feature = "libfacedetection")] { result.push(magnus::Symbol::new("libfacedetection"))?; } diff --git a/libfacedetection.gemspec b/libfacedetection.gemspec index 92d8adc..1570856 100644 --- a/libfacedetection.gemspec +++ b/libfacedetection.gemspec @@ -19,11 +19,11 @@ Gem::Specification.new do |spec| spec.files = Dir[ "Cargo.toml", + "Cargo.lock", "lib/**/*.rb", "ext/**/*", "*.gemspec", "README.md", - "LICENSE.txt" ].reject do |path| path.match?(%r{\A(?:lib/libfacedetection/(?:.*\.(?:bundle|so|dll|dylib))|ext/libfacedetection/(?:Makefile|mkmf\.log|target/|.*\.(?:bundle|so|dll)))}) end From fd547384456a79e945dd8abc795e3dd148fe5020 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 14:17:34 +0100 Subject: [PATCH 11/12] Replace deprecated Magnus APIs --- .github/workflows/workflow.yml | 2 - ext/libfacedetection/src/lib.rs | 106 +++++++++++++++++++------------- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1b02c5d..cba857a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -22,8 +22,6 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - rustflags: "" - name: Install libvips on Linux if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y libvips diff --git a/ext/libfacedetection/src/lib.rs b/ext/libfacedetection/src/lib.rs index 567b9af..b589979 100644 --- a/ext/libfacedetection/src/lib.rs +++ b/ext/libfacedetection/src/lib.rs @@ -1,22 +1,22 @@ -use magnus::{error::Result, exception, Error, Integer, RArray, RHash, Symbol}; +use magnus::{error::Result, Error, RArray, Ruby}; #[cfg(feature = "opencv")] -fn detect_opencv(content: Vec) -> Result { +fn detect_opencv(ruby: &Ruby, content: Vec) -> Result { use opencv::{core::Size, imgcodecs, imgproc, objdetect, prelude::*, types}; let cascade_file_path = opencv::core::find_file("haarcascades/haarcascade_frontalface_alt.xml", true, false) - .map_err(|e| Error::new(exception::runtime_error(), e.to_string()))?; + .map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?; let mut classifier = objdetect::CascadeClassifier::new(&cascade_file_path).map_err(|e| { Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), format!("failed creating classifier: {}", e), ) })?; let img = imgcodecs::imdecode(&types::VectorOfu8::from(content), imgproc::COLOR_BGR2GRAY) .map_err(|e| { Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), format!("failed decoding image: {}", e), ) })?; @@ -33,39 +33,45 @@ fn detect_opencv(content: Vec) -> Result { ) .map_err(|e| { Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), format!("Failed to run detect_multi_scale: {}", e), ) })?; - let result = RArray::new(); + let result = ruby.ary_new(); for face in faces { - let hash = RHash::new(); - // let landmarks = RArray::new(); - hash.aset(Symbol::new("x"), Integer::from_i64(face.x as i64))?; - hash.aset(Symbol::new("y"), Integer::from_i64(face.y as i64))?; - hash.aset(Symbol::new("width"), Integer::from_i64(face.width as i64))?; - hash.aset(Symbol::new("height"), Integer::from_i64(face.height as i64))?; + let hash = ruby.hash_new(); + hash.aset(ruby.to_symbol("x"), ruby.integer_from_i64(face.x as i64))?; + hash.aset(ruby.to_symbol("y"), ruby.integer_from_i64(face.y as i64))?; + hash.aset( + ruby.to_symbol("width"), + ruby.integer_from_i64(face.width as i64), + )?; + hash.aset( + ruby.to_symbol("height"), + ruby.integer_from_i64(face.height as i64), + )?; result.push(hash)?; } Ok(result) } #[cfg(all(feature = "libfacedetection", feature = "opencv"))] -fn detect_libfacedetection(content: Vec) -> Result { +fn detect_libfacedetection(ruby: &Ruby, content: Vec) -> Result { use opencv::{imgcodecs, prelude::*, types}; let img = imgcodecs::imdecode(&types::VectorOfu8::from(content), imgcodecs::IMREAD_COLOR) .map_err(|e| { Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), format!("unable to decode image: {}", e), ) })?; detect_libfacedetection_data( + ruby, img.ptr(0).map_err(|e| { Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), format!("unable to read image data: {}", e), ) })?, @@ -76,23 +82,24 @@ fn detect_libfacedetection(content: Vec) -> Result { } #[cfg(not(feature = "opencv"))] -fn detect_opencv(_content: Vec) -> Result { +fn detect_opencv(ruby: &Ruby, _content: Vec) -> Result { Err(Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), "OpenCV is not enabled", )) } #[cfg(not(all(feature = "libfacedetection", feature = "opencv")))] -fn detect_libfacedetection(_content: Vec) -> Result { +fn detect_libfacedetection(ruby: &Ruby, _content: Vec) -> Result { Err(Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), "need to have both libfacedetection and OpenCV enabled", )) } #[cfg(feature = "libfacedetection")] fn detect_libfacedetection_data( + ruby: &Ruby, bgrdata: *const u8, width: i32, height: i32, @@ -106,59 +113,72 @@ fn detect_libfacedetection_data( ) .map_err(|e| { Error::new( - exception::runtime_error(), + ruby.exception_runtime_error(), format!("libfacedetection error: {}", e), ) })?; - let result = RArray::new(); + let result = ruby.ary_new(); for face in facedetect_result.faces { - let hash = RHash::new(); - let landmarks = RArray::new(); - hash.aset(Symbol::new("x"), Integer::from_i64(face.x as i64))?; - hash.aset(Symbol::new("y"), Integer::from_i64(face.y as i64))?; - hash.aset(Symbol::new("width"), Integer::from_i64(face.width as i64))?; - hash.aset(Symbol::new("height"), Integer::from_i64(face.height as i64))?; + let hash = ruby.hash_new(); + let landmarks = ruby.ary_new(); + hash.aset(ruby.to_symbol("x"), ruby.integer_from_i64(face.x as i64))?; + hash.aset(ruby.to_symbol("y"), ruby.integer_from_i64(face.y as i64))?; + hash.aset( + ruby.to_symbol("width"), + ruby.integer_from_i64(face.width as i64), + )?; + hash.aset( + ruby.to_symbol("height"), + ruby.integer_from_i64(face.height as i64), + )?; hash.aset( - Symbol::new("confidence"), - Integer::from_i64(face.confidence as i64), + ruby.to_symbol("confidence"), + ruby.integer_from_i64(face.confidence as i64), )?; for landmark in face.landmarks { - let a = RArray::new(); - a.push(Integer::from_i64(landmark.0 as i64))?; - a.push(Integer::from_i64(landmark.1 as i64))?; + let a = ruby.ary_new(); + a.push(ruby.integer_from_i64(landmark.0 as i64))?; + a.push(ruby.integer_from_i64(landmark.1 as i64))?; landmarks.push(a)?; } - hash.aset(Symbol::new("landmarks"), landmarks)?; + hash.aset(ruby.to_symbol("landmarks"), landmarks)?; result.push(hash)?; } Ok(result) } -fn pub_detect_opencv(content: String) -> Result { - detect_opencv(content.into_bytes()) +fn pub_detect_opencv(ruby: &Ruby, content: String) -> Result { + detect_opencv(ruby, content.into_bytes()) } -fn pub_detect_libfacedetection(content: String) -> Result { - detect_libfacedetection(content.into_bytes()) +fn pub_detect_libfacedetection(ruby: &Ruby, content: String) -> Result { + detect_libfacedetection(ruby, content.into_bytes()) } fn pub_detect_libfacedetection_image_data( + ruby: &Ruby, data_ptr: usize, width: usize, height: usize, ) -> Result { - detect_libfacedetection_data(data_ptr as *const u8, width as i32, height as i32, None) + detect_libfacedetection_data( + ruby, + data_ptr as *const u8, + width as i32, + height as i32, + None, + ) } -fn features() -> Result { - let result = RArray::new(); +fn features(ruby: &Ruby) -> Result { + let result = ruby.ary_new(); #[cfg(feature = "libfacedetection")] { - result.push(magnus::Symbol::new("libfacedetection"))?; + result.push(ruby.to_symbol("libfacedetection"))?; } #[cfg(feature = "opencv")] { - result.push(magnus::Symbol::new("opencv"))?; + result.push(ruby.to_symbol("opencv"))?; } Ok(result) } From 81aa369bbb59a18deabbec7410fdb0a5b44d1156 Mon Sep 17 00:00:00 2001 From: Antonis Berkakis Date: Thu, 11 Jun 2026 15:19:26 +0100 Subject: [PATCH 12/12] Match rb-sys root extension layout --- Cargo.toml | 23 +++++++++++++-- ext/libfacedetection/Cargo.toml | 20 ------------- ext/libfacedetection/extconf.rb | 7 +---- extconf.rb | 12 ++++++++ lib/libfacedetection.rb | 36 ++++++++++++++---------- libfacedetection.gemspec | 2 ++ {ext/libfacedetection/src => src}/lib.rs | 2 +- 7 files changed, 57 insertions(+), 45 deletions(-) delete mode 100644 ext/libfacedetection/Cargo.toml create mode 100644 extconf.rb rename {ext/libfacedetection/src => src}/lib.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index 719949e..8460d84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,20 @@ -[workspace] -members = ["ext/*"] -resolver = "2" +[package] +name = "libfacedetection" +version = "0.0.1" +edition = "2021" +publish = false +authors = ["Fetlife "] + +[lib] +name = "libfacedetection" +crate-type = ["cdylib"] + +[dependencies] +libfacedetection_rs = { package = "libfacedetection", git = "https://github.com/fetlife/libfacedetection-rs.git", optional = true, rev = "7426f33ba101514932a5ef58456761735c7bf3dc" } +opencv = { version = "0.88.6", optional = true, features = ["clang-runtime"] } +magnus = { version = "0.8.2" } +rb-sys = { version = "0.9.126", default-features = false, features = ["stable-api-compiled-fallback"] } + +[features] +default = ["libfacedetection"] +libfacedetection = ["dep:libfacedetection_rs"] diff --git a/ext/libfacedetection/Cargo.toml b/ext/libfacedetection/Cargo.toml deleted file mode 100644 index 8460d84..0000000 --- a/ext/libfacedetection/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "libfacedetection" -version = "0.0.1" -edition = "2021" -publish = false -authors = ["Fetlife "] - -[lib] -name = "libfacedetection" -crate-type = ["cdylib"] - -[dependencies] -libfacedetection_rs = { package = "libfacedetection", git = "https://github.com/fetlife/libfacedetection-rs.git", optional = true, rev = "7426f33ba101514932a5ef58456761735c7bf3dc" } -opencv = { version = "0.88.6", optional = true, features = ["clang-runtime"] } -magnus = { version = "0.8.2" } -rb-sys = { version = "0.9.126", default-features = false, features = ["stable-api-compiled-fallback"] } - -[features] -default = ["libfacedetection"] -libfacedetection = ["dep:libfacedetection_rs"] diff --git a/ext/libfacedetection/extconf.rb b/ext/libfacedetection/extconf.rb index 45d3f0d..e0c7e9b 100644 --- a/ext/libfacedetection/extconf.rb +++ b/ext/libfacedetection/extconf.rb @@ -1,8 +1,3 @@ # frozen_string_literal: true -require "mkmf" -require "rb_sys/mkmf" - -create_rust_makefile("libfacedetection/libfacedetection") do |config| - config.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", "release").to_sym -end +load File.expand_path("../../extconf.rb", __dir__) diff --git a/extconf.rb b/extconf.rb new file mode 100644 index 0000000..ca688da --- /dev/null +++ b/extconf.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "mkmf" +require "pathname" +require "rb_sys/mkmf" + +create_rust_makefile("libfacedetection") do |config| + relative_ext_dir = Pathname(__dir__).relative_path_from(Pathname.pwd) + # rb_sys strips one leading "./" while constructing the Cargo manifest path. + config.ext_dir = "./#{relative_ext_dir}" + config.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", "release").to_sym +end diff --git a/lib/libfacedetection.rb b/lib/libfacedetection.rb index 324bbab..2fa1823 100644 --- a/lib/libfacedetection.rb +++ b/lib/libfacedetection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "libfacedetection/version" module Libfacedetection @@ -12,22 +14,26 @@ def available? end end -begin - [ - "libfacedetection/#{Libfacedetection::RUBY_API_VERSION}/libfacedetection", - "libfacedetection/libfacedetection", - "../ext/libfacedetection/libfacedetection" - ].each do |path| - begin - require_relative path - Libfacedetection.instance_variable_set(:@available, true) - break - rescue LoadError - next - end - end +[ + "libfacedetection/#{Libfacedetection::RUBY_API_VERSION}/libfacedetection", + "libfacedetection/libfacedetection", + "../ext/libfacedetection/libfacedetection", +].each do |path| + require_relative path + Libfacedetection.instance_variable_set(:@available, true) + break rescue LoadError - Libfacedetection.instance_variable_set(:@available, false) + next +end + +unless Libfacedetection.available? + begin + extension_dir = Gem.loaded_specs["libfacedetection"]&.extension_dir if defined?(Gem) + require File.join(extension_dir, "libfacedetection") if extension_dir + Libfacedetection.instance_variable_set(:@available, true) + rescue LoadError + nil + end end Libfacedetection.instance_variable_set(:@available, false) unless Libfacedetection.available? diff --git a/libfacedetection.gemspec b/libfacedetection.gemspec index 1570856..715a8d4 100644 --- a/libfacedetection.gemspec +++ b/libfacedetection.gemspec @@ -22,6 +22,8 @@ Gem::Specification.new do |spec| "Cargo.lock", "lib/**/*.rb", "ext/**/*", + "extconf.rb", + "src/**/*.rs", "*.gemspec", "README.md", ].reject do |path| diff --git a/ext/libfacedetection/src/lib.rs b/src/lib.rs similarity index 99% rename from ext/libfacedetection/src/lib.rs rename to src/lib.rs index b589979..d28a700 100644 --- a/ext/libfacedetection/src/lib.rs +++ b/src/lib.rs @@ -184,7 +184,7 @@ fn features(ruby: &Ruby) -> Result { } #[magnus::init] -fn init(ruby: &magnus::Ruby) -> Result<()> { +fn init(ruby: &Ruby) -> Result<()> { let module = ruby.define_module("Libfacedetection")?; module.define_module_function("detect_opencv", magnus::function!(pub_detect_opencv, 1))?; module.define_module_function(