Skip to content

Commit

Permalink
fix: Error when loading and installing native extension (#1)
Browse files Browse the repository at this point in the history
* Bump bytebuffer to 0.1.3

* fix: remove ext artifacts from gemspec

* fix: update structure

* fix: path for install

* feat: use rb_sys to compile extension and generate makefile, update directory for shared object search

* fix: rake task for building

* feat: sigh heavily and add a patch system

Certain platforms (ubuntu) produce unexpected behavior. This patch system should address that by running platform-specific patches in the `lib/bytebuffer/patches` dir after the native extension is compiled and before the gem is packaged. Patches have the potential to introduce performance penalties.

* feat: run tests on the current ref

* fix: run patch in test

* fix: guard patch application

* fix: guard patch application

* fix: doh!

* fix: reduce mingw targets to prevent dependency collision

* feat: windows support? syke.
  • Loading branch information
Sickday authored Jul 20, 2024
1 parent 0569a43 commit 04149d6
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 103 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ jobs:
strategy:
max-parallel: 3
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
os: [ ubuntu-latest, macos-latest ]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand All @@ -30,7 +32,9 @@ jobs:
rust-version: stable

- name: Build extension
run: bundle exec rake gem:build
run: |
bundle exec rake gem:compile
bundle exec rake gem:patch
- name: Run Rspec
run: bundle exec rake test:rspec
Expand Down
14 changes: 7 additions & 7 deletions ext/Cargo.lock → Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[workspace]
members = ["./ext/bytebuffer"]
resolver = "2"
7 changes: 6 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
bytebuffer (0.1.2)
bytebuffer (0.1.3)
ffi (~> 1.17)

GEM
Expand All @@ -21,6 +21,9 @@ GEM
ffi (1.17.0-x86_64-linux-gnu)
ffi (1.17.0-x86_64-linux-musl)
rake (13.2.1)
rake-compiler (1.2.7)
rake
rb_sys (0.9.98)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
Expand Down Expand Up @@ -59,6 +62,8 @@ PLATFORMS
DEPENDENCIES
bytebuffer!
rake (~> 13.0)
rake-compiler (~> 1.2)
rb_sys (~> 0.9)
rspec (~> 3.12)
simplecov
webrick (~> 1.7)
Expand Down
91 changes: 65 additions & 26 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,84 @@ require 'bundler/gem_tasks'
require 'ffi'
require 'rake'
require 'rake/testtask'
require 'rb_sys/extensiontask'
require 'rspec/core/rake_task'
require 'rubygems'
require 'rubygems/package_task'

GEM_SPEC = Gem::Specification.load('bytebuffer.gemspec')

namespace :gem do
desc 'Build the Gem extensions'
task :build do
raise "Install rustc along with Cargo before running this rake task." if !system('cargo --version') || !system('rustc --version')

FileUtils.chdir("ext/") do
target = case RUBY_PLATFORM
when /darwin/ then 'aarch64-apple-darwin'
when /linux/ then 'x86_64-unknown-linux-gnu'
when /mswin|mingw/ then 'x86_64-pc-windows-msvc'
else 'x86_64-unknown-linux-gnu'
end
desc 'Clean up the Gem build environment. Note: This command indiscriminately removes all .so, and .bundle files in subdirectories.'
task :clean do
FileUtils.rm_rf(%w(./tmp ./pkg ./target))

target_path = "./build/#{target}/release/#{RUBY_PLATFORM.match?(/mswin|mingw/) ? 'ext' : 'libext'}.#{FFI::Platform::LIBSUFFIX}"
case FFI::Platform::OS
when 'linux' then FileUtils.rm_r(Dir['./**/*.so'])
when 'darwin' then FileUtils.rm_r(Dir['./**/*.bundle'])
else system("rm -r ./**/*.so")
end
end

FileUtils.mkdir("build")
desc 'Compile and install the native extension'
task compile: %i(clean) do
RbSys::ExtensionTask.new('bytebuffer', GEM_SPEC) do |ext|
ext.source_pattern = "*.{rs,toml,lock,rb}"
ext.ext_dir = "#{File.dirname(__FILE__)}/ext/bytebuffer"
ext.cross_platform = %w[
aarch64-linux
arm64-darwin
x86_64-darwin
x86_64-linux
x86_64-linux-musl
]

ext.cross_compile = true
ext.lib_dir = "lib"
end

system("cargo build --release --target=#{target} --target-dir=#{FileUtils.pwd}/build")
Rake::Task["compile"].invoke
end

FileUtils.cp(target_path, "./libext.#{FFI::Platform::LIBSUFFIX}")
desc 'Apply platform patches'
task :patch do
working_dir = File.dirname(__FILE__)
target = case RUBY_PLATFORM
when /darwin/ then 'darwin'
when /linux/ then 'linux'
end
patch_dir = "#{working_dir}/lib/bytebuffer/patch/#{target}"
unless !File.directory?(patch_dir) || Dir.empty?(patch_dir)
puts "Applying platform patches for #{target}"

FileUtils.chdir(patch_dir) do |dir|
Dir["#{dir}/*.patch"].sort.each do |patch|
puts "Applying patch: #{patch}"
system("patch #{working_dir}/lib/bytebuffer.rb #{patch}")
end
end
end
end

desc 'Clean up the Gem build environment.'
task :clean do
FileUtils.rm_rf('pkg/')
FileUtils.rm_rf('ext/build/')
desc 'Revert platform patches'
task :revert do
working_dir = File.dirname(__FILE__)
target = case RUBY_PLATFORM
when /darwin/ then 'darwin'
when /linux/ then 'linux'
end

FileUtils.chdir("#{working_dir}/lib/bytebuffer/patch/#{target}") do |dir|
Dir["#{dir}/*.patch"].sort.each do |patch|
puts "Applying patch: #{patch}"
system("patch -R #{working_dir}/lib/bytebuffer.rb #{patch}")
end
end
end

desc 'Package the Gem package'
task :package do
load('bytebuffer.gemspec')

task package: %i(compile gem:patch) do
Gem::PackageTask.new(GEM_SPEC) do |pkg|
pkg.need_tar_bz2 = true
end
Expand All @@ -53,7 +94,7 @@ namespace :docs do

YARD::Rake::YardocTask.new do |t|
t.name = 'generate'
t.files = ['lib/**/*.rb']
t.files = %w(lib/**/*.rb)
end

task serve: %i[docs:generate] do
Expand Down Expand Up @@ -83,8 +124,6 @@ namespace :test do
task :ext do
raise "Install rustc along with Cargo before running this rake task." if !system('cargo --version') || !system('rustc --version')

FileUtils.chdir("ext/") do
system("cargo test")
end
system("cargo test")
end
end
end
16 changes: 12 additions & 4 deletions bytebuffer.gemspec
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
require_relative 'lib/bytebuffer/version'

GEM_SPEC = Gem::Specification.new do |spec|
Gem::Specification.new do |spec|
spec.name = 'bytebuffer'
spec.version = ByteBuffer::VERSION
spec.summary = 'A gem wrapping the bytebuffer crate using FFI.'
spec.description = 'A gem that provides a ByteBuffer class based on the bytebuffer rust crate.'
spec.homepage = 'https://github.com/sickday/bytebuffer-rb'
spec.license = 'BSD-3-Clause'
spec.author = 'Patrick W.'
spec.email = '[email protected]'

spec.metadata = {
"homepage_uri" => 'https://github.com/sickday/bytebuffer-rb',
"documentation_uri" => 'https://sickday.github.io/bytebuffer-rb/'
}

spec.required_ruby_version = '>= 3.1.0'

spec.add_runtime_dependency 'ffi', '~> 1.17'

spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rake-compiler', '~> 1.2'
spec.add_development_dependency 'rb_sys', '~> 0.9'
spec.add_development_dependency 'rspec', '~> 3.12'

spec.extensions = "ext/extconf.rb"
spec.require_paths = ['lib']
spec.extensions = %w(ext/bytebuffer/extconf.rb)
spec.require_paths = %w(lib)

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + Dir["ext/*.so"] + Dir["ext/*.dylib"] + Dir["ext/*.dll"]
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|git)/}) }
end
59 changes: 0 additions & 59 deletions ext/Makefile

This file was deleted.

4 changes: 3 additions & 1 deletion ext/Cargo.toml → ext/bytebuffer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[package]
name = "ext"
name = "bytebuffer"
version = "0.1.0"
edition = "2021"
authors = ["Patrick Worlds <[email protected]>"]
publish = false

[lib]
crate-type = ["cdylib"]
Expand Down
9 changes: 9 additions & 0 deletions ext/bytebuffer/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "mkmf"
require "rb_sys/mkmf"

create_rust_makefile("bytebuffer") do |bb|
bb.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", :dev).to_sym
bb.ext_dir = "."
bb.clean_after_install = true
bb.auto_install_rust_toolchain = true
end
File renamed without changes.
1 change: 0 additions & 1 deletion ext/extconf.rb

This file was deleted.

Binary file removed ext/libext.dylib
Binary file not shown.
6 changes: 5 additions & 1 deletion lib/bytebuffer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
module ByteBufferExtension
extend FFI::Library

ffi_lib("ext/libext.#{FFI::Platform::LIBSUFFIX}")
case FFI::Platform::OS
when 'linux' then ffi_lib ["#{File.dirname(__FILE__)}/bytebuffer.so"]
when 'darwin' then ffi_lib ["#{File.dirname(__FILE__)}/bytebuffer.bundle"]
else ffi_lib ["#{File.dirname(__FILE__)}/bytebuffer.so"]
end

attach_function(:new, :new, [], :pointer)
attach_function(:from_bytes, :from_bytes, [:pointer, :int], :pointer)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--- lib/bytebuffer.rb.orig 2024-07-20 14:25:37
+++ lib/bytebuffer.rb 2024-07-20 14:26:17
@@ -178,11 +178,14 @@

# Read a single 8-bit unsigned integer from the {ByteBuffer}.
# @return [Integer] the read value.
- def read_u8; ByteBufferExtension.read_u8(@ptr); end
+ def read_u8; ByteBufferExtension.read_u8(@ptr) & 0xff; end

# Read a single 8-bit signed integer from the {ByteBuffer}.
# @return [Integer] the read value.
- def read_i8; ByteBufferExtension.read_i8(@ptr); end
+ def read_i8
+ v=ByteBufferExtension.read_i8(@ptr)
+ (v&(0xff/2))-(v&(0xff/2+1))
+ end

# Read a single 16-bit unsigned integer from the {ByteBuffer}.
# @return [Integer] the read value.
@@ -190,7 +193,10 @@

# Read a single 16-bit signed integer from the {ByteBuffer}.
# @return [Integer] the read value.
- def read_i16; ByteBufferExtension.read_i16(@ptr); end
+ def read_i16
+ v=ByteBufferExtension.read_i16(@ptr)
+ (v&(0xffff/2))-(v&(0xffff/2+1))
+ end

# Read a single 32-bit unsigned integer from the {ByteBuffer}.
# @return [Integer] the read value.
2 changes: 1 addition & 1 deletion lib/bytebuffer/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class ByteBuffer
# @!attribute [r] VERSION
# @return [Integer] version number of the library
VERSION = '0.1.2'
VERSION = '0.1.3'
end

0 comments on commit 04149d6

Please sign in to comment.