direct-bind 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50ced79cb992ea76c0be6b1935d914d985a2026c3066846f238a8b75102fe021
4
- data.tar.gz: fa785de50c2b7d4fda46dfd5b6cbfded13a480eafd15d5dd3ad81c6d90a060fc
3
+ metadata.gz: ee80345a659c00cc5dc666e40baf81c76c0c42a08b7bc337ed75087ab0d1514f
4
+ data.tar.gz: ce718f097ba34a0858f1765bf475eeda66296af4f1fe12f5441ffdf4e1557177
5
5
  SHA512:
6
- metadata.gz: 45f73729515bcd061b2d363dbbe14944238abfe1098b8d5eb16da8ecb81594a4937b75da89308d6598f08565318ac68238401a7f4f02891b36fdd592bcfe24d2
7
- data.tar.gz: 39fd3358e2be05a370ec0e30e9dca8a75b8635e46fa372afb1dbe721c1ddc98c747002404060f20245a642a3d658db0d673a535ebc57bc8610d03773569a8359
6
+ metadata.gz: 0357cc24a731f4a3ec624211deaa743009a432e996339bd4bb0d8a2322ee1d4feca27a728f1f08e16a416c50180dbaa869c0a6a53c0661f1c6ff83fc597a3bb1
7
+ data.tar.gz: 3527f022c312154c455ca4220a694531427dbf08f0bd436c63005f1823e7c60c0bb300d531dd4f71d0e808f52e23e0c71dbf698141ed9038ecb4885f15084a9e
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/direct-bind.gemspec CHANGED
@@ -42,10 +42,9 @@ Gem::Specification.new do |spec|
42
42
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
43
43
  spec.files = Dir.chdir(__dir__) do
44
44
  `git ls-files -z`.split("\x0").reject do |f|
45
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|examples)/|\.(?:git|travis|circleci)|appveyor)}) ||
45
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|examples|ext)/|\.(?:git|travis|circleci)|appveyor)}) ||
46
46
  [".editorconfig", ".ruby-version", ".standard.yml", "gems.rb", "Rakefile"].include?(f)
47
47
  end
48
48
  end
49
- spec.require_paths = ["lib", "ext"]
50
- spec.extensions = ["ext/direct_bind_native_extension/extconf.rb"]
49
+ spec.require_paths = ["lib"]
51
50
  end
@@ -23,7 +23,23 @@
23
23
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  // SOFTWARE.
25
25
 
26
- #include "direct_bind.h"
26
+ // See direct-bind.h for details on using direct-bind and why you may be finding this file vendored inside another gem.
27
+
28
+ #include "direct-bind.h"
29
+
30
+ static bool direct_bind_self_test(bool raise_on_failure);
31
+
32
+ // # Initialization and version management
33
+
34
+ bool direct_bind_initialize(VALUE publish_version_under, bool raise_on_failure) {
35
+ if (!direct_bind_self_test(raise_on_failure)) return false;
36
+
37
+ if (publish_version_under != Qnil) {
38
+ rb_define_const(rb_define_module_under(publish_version_under, "DirectBind"), "VERSION", rb_str_new_lit(DIRECT_BIND_VERSION));
39
+ }
40
+
41
+ return true;
42
+ }
27
43
 
28
44
  // # Self-test implementation
29
45
 
@@ -38,16 +54,14 @@ static VALUE self_test_target_func(
38
54
  return Qnil;
39
55
  }
40
56
 
41
- bool direct_bind_self_test(bool raise_on_failure) {
57
+ static bool direct_bind_self_test(bool raise_on_failure) {
42
58
  VALUE anonymous_module = rb_module_new();
43
59
  rb_define_method(anonymous_module, "direct_bind_self_test_target", self_test_target_func, SELF_TEST_ARITY);
44
60
 
45
61
  ID self_test_id = rb_intern("direct_bind_self_test_target");
46
- direct_bind_cfunc_result test_target = direct_bind_get_cfunc(anonymous_module, self_test_id, raise_on_failure);
62
+ direct_bind_cfunc_result test_target = direct_bind_get_cfunc_with_arity(anonymous_module, self_test_id, SELF_TEST_ARITY, raise_on_failure);
47
63
 
48
- if (!test_target.ok) return false;
49
-
50
- return test_target.arity == SELF_TEST_ARITY && test_target.func == self_test_target_func;
64
+ return test_target.ok && test_target.func == self_test_target_func;
51
65
  }
52
66
 
53
67
  // # Structure layouts and exported symbol definitions from Ruby
@@ -101,6 +115,19 @@ direct_bind_cfunc_result direct_bind_get_cfunc(VALUE klass, ID method_name, bool
101
115
  return find_data.result;
102
116
  }
103
117
 
118
+ direct_bind_cfunc_result direct_bind_get_cfunc_with_arity(VALUE klass, ID method_name, int arity, bool raise_on_failure) {
119
+ direct_bind_cfunc_result result = direct_bind_get_cfunc(klass, method_name, raise_on_failure);
120
+
121
+ if (result.ok && result.arity != arity) {
122
+ VALUE unexpected_arity = rb_sprintf("method %"PRIsVALUE".%"PRIsVALUE" unexpected arity %d, expected %d", klass, ID2SYM(method_name), result.arity, arity);
123
+
124
+ if (raise_on_failure) rb_raise(rb_eRuntimeError, "direct_bind_get_cfunc_with_arity failed: %"PRIsVALUE, unexpected_arity);
125
+ else result = (direct_bind_cfunc_result) {.ok = false, .failure_reason = unexpected_arity};
126
+ }
127
+
128
+ return result;
129
+ }
130
+
104
131
  // TODO: Maybe change this to use safe memory reads that can never segv (e.g. if structure layouts are off?)
105
132
  static int find_cfunc(void *start, void *end, size_t stride, void *data) {
106
133
  const int stop_iteration = 1;
@@ -23,11 +23,22 @@
23
23
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  // SOFTWARE.
25
25
 
26
+ // The recommended way to consume the direct-bind gem is to always vendor it.
27
+ // That is, use its rake task to automatically copy direct-bind.h and direct-bind.c into another gem's native extension
28
+ // sources folder. (P.s.: There's also a test helper to make sure copying is working fine and the gem is up-to-date.)
29
+ //
30
+ // This makes the actual Ruby direct-bind gem only a development dependency, simplifying distribution for the gem
31
+ // that uses it.
32
+ //
33
+ // For more details, check the direct-bind gem's documentation.
34
+
26
35
  #pragma once
27
36
 
28
37
  #include <stdbool.h>
29
38
  #include <ruby.h>
30
39
 
40
+ #define DIRECT_BIND_VERSION "1.0.0"
41
+
31
42
  typedef struct {
32
43
  bool ok;
33
44
  VALUE failure_reason;
@@ -35,8 +46,9 @@ typedef struct {
35
46
  VALUE (*func)(ANYARGS);
36
47
  } direct_bind_cfunc_result;
37
48
 
38
- // Recommended to call once during your gem's initialization, to validate that direct-bind's Ruby hacking is in good shape.
39
- bool direct_bind_self_test(bool raise_on_failure);
49
+ // Recommended to call once during your gem's initialization, to validate that direct-bind's Ruby hacking is in good shape and
50
+ // to make it easy to (optionally) validate what version you're using
51
+ bool direct_bind_initialize(VALUE publish_version_under, bool raise_on_failure);
40
52
 
41
53
  // Provides the reverse of `rb_define_method`: Given a class and a method_name, retrieves the arity and func previously
42
54
  // passed to `rb_define_method`.
@@ -44,3 +56,6 @@ bool direct_bind_self_test(bool raise_on_failure);
44
56
  // Performance note: As of this writing, this method scans objspace to find the definition of the method, so you
45
57
  // most probably want to cache its result, rather than calling it very often.
46
58
  direct_bind_cfunc_result direct_bind_get_cfunc(VALUE klass, ID method_name, bool raise_on_failure);
59
+
60
+ // Same as above, but automatically fails if arity isn't the expected value
61
+ direct_bind_cfunc_result direct_bind_get_cfunc_with_arity(VALUE klass, ID method_name, int arity, bool raise_on_failure);
data/lib/direct-bind.rb CHANGED
@@ -27,7 +27,5 @@
27
27
 
28
28
  require_relative "direct_bind/version"
29
29
 
30
- require "direct_bind_native_extension"
31
-
32
30
  module DirectBind
33
31
  end
@@ -0,0 +1,58 @@
1
+ # direct-bind: Ruby gem for getting direct access to function pointers
2
+ # Copyright (c) 2025 Ivo Anjo <ivo@ivoanjo.me>
3
+ #
4
+ # This file is part of direct-bind.
5
+ #
6
+ # MIT License
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in all
16
+ # copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+
26
+ # frozen_string_literal: true
27
+
28
+ require "rake"
29
+ require "rake/tasklib"
30
+
31
+ module DirectBind
32
+ module Rake
33
+ class InstallTask < ::Rake::TaskLib
34
+ DIRECT_BIND_SOURCES = ["direct-bind.h", "direct-bind.c"]
35
+ DIRECT_BIND_SOURCES_PATH = File.join(Gem.loaded_specs["direct-bind"].full_gem_path, "dist")
36
+
37
+ def initialize(extension_name)
38
+ target_extension_path = File.join(Dir.pwd, "ext", extension_name)
39
+
40
+ desc "Install direct_bind files into extension"
41
+ task(:"direct-bind:install") do
42
+ DIRECT_BIND_SOURCES.each do |file_name|
43
+ from_path = File.join(DIRECT_BIND_SOURCES_PATH, file_name)
44
+ to_path = File.join(target_extension_path, file_name)
45
+
46
+ FileUtils.cp(from_path, to_path) unless already_up_to_date?(from_path, to_path)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def already_up_to_date?(file1, file2) # Order doesn't really matter here
54
+ File.exist?(file1) && File.exist?(file2) && FileUtils.compare_file(file1, file2)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -23,24 +23,17 @@
23
23
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  # SOFTWARE.
25
25
 
26
- if ["jruby", "truffleruby"].include?(RUBY_ENGINE)
27
- raise \
28
- "\n#{"-" * 80}\nSorry! This gem is unsupported on #{RUBY_ENGINE}. Since it relies on a lot of guts of MRI Ruby, " \
29
- "it's impossible to make a direct port.\n" \
30
- "Perhaps a #{RUBY_ENGINE} equivalent could be created -- help is welcome! :)\n#{"-" * 80}"
31
- end
26
+ # frozen_string_literal: true
32
27
 
33
- require "mkmf"
28
+ require "direct-bind"
29
+ require "rspec/expectations"
34
30
 
35
- append_cflags("-std=gnu99")
36
- append_cflags("-Wno-declaration-after-statement")
37
- append_cflags("-Wno-compound-token-split-by-macro")
38
- append_cflags("-Werror-implicit-function-declaration")
39
- append_cflags("-Wunused-parameter")
40
- append_cflags("-Wold-style-definition")
41
- append_cflags("-Wall")
42
- append_cflags("-Wextra")
43
- append_cflags("-Werror") if ENV["ENABLE_WERROR"] == "true"
31
+ module DirectBind
32
+ module RSpecHelper
33
+ self.class.include RSpec::Matchers
44
34
 
45
- create_header
46
- create_makefile "direct_bind_native_extension"
35
+ def self.expect_direct_bind_version_to_be_up_to_date_in(native_extension_module)
36
+ expect(native_extension_module::DirectBind::VERSION).to eq ::DirectBind::VERSION
37
+ end
38
+ end
39
+ end
@@ -26,5 +26,6 @@
26
26
  # frozen_string_literal: true
27
27
 
28
28
  module DirectBind
29
- VERSION = "0.1.1"
29
+ # Must be kept in sync with the VERSION in `dist/direct-bind.h` (and there's a test for it)
30
+ VERSION = "1.0.0"
30
31
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: direct-bind
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivo Anjo
@@ -12,19 +12,19 @@ dependencies: []
12
12
  email:
13
13
  - ivo@ivoanjo.me
14
14
  executables: []
15
- extensions:
16
- - ext/direct_bind_native_extension/extconf.rb
15
+ extensions: []
17
16
  extra_rdoc_files: []
18
17
  files:
18
+ - ".rspec"
19
19
  - CODE_OF_CONDUCT.adoc
20
20
  - LICENSE
21
21
  - README.adoc
22
22
  - direct-bind.gemspec
23
- - ext/direct_bind_native_extension/direct_bind.c
24
- - ext/direct_bind_native_extension/direct_bind.h
25
- - ext/direct_bind_native_extension/direct_bind_native_extension.c
26
- - ext/direct_bind_native_extension/extconf.rb
23
+ - dist/direct-bind.c
24
+ - dist/direct-bind.h
27
25
  - lib/direct-bind.rb
26
+ - lib/direct_bind/rake.rb
27
+ - lib/direct_bind/rspec_helper.rb
28
28
  - lib/direct_bind/version.rb
29
29
  homepage: https://github.com/ivoanjo/direct-bind
30
30
  licenses:
@@ -33,7 +33,6 @@ metadata: {}
33
33
  rdoc_options: []
34
34
  require_paths:
35
35
  - lib
36
- - ext
37
36
  required_ruby_version: !ruby/object:Gem::Requirement
38
37
  requirements:
39
38
  - - ">="
@@ -1,41 +0,0 @@
1
- // direct-bind: Ruby gem for getting direct access to function pointers
2
- // Copyright (c) 2025 Ivo Anjo <ivo@ivoanjo.me>
3
- //
4
- // This file is part of direct-bind.
5
- //
6
- // MIT License
7
- //
8
- // Permission is hereby granted, free of charge, to any person obtaining a copy
9
- // of this software and associated documentation files (the "Software"), to deal
10
- // in the Software without restriction, including without limitation the rights
11
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
- // copies of the Software, and to permit persons to whom the Software is
13
- // furnished to do so, subject to the following conditions:
14
- //
15
- // The above copyright notice and this permission notice shall be included in all
16
- // copies or substantial portions of the Software.
17
- //
18
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
- // SOFTWARE.
25
-
26
- #include "direct_bind.h"
27
-
28
- VALUE direct_bind_call(VALUE _self, VALUE klass, VALUE method, VALUE instance);
29
-
30
- void Init_direct_bind_native_extension(void) {
31
- direct_bind_self_test(true);
32
-
33
- VALUE direct_bind_module = rb_define_module("DirectBind");
34
- rb_define_singleton_method(direct_bind_module, "call", direct_bind_call, 3);
35
- }
36
-
37
- VALUE direct_bind_call(__attribute__((unused)) VALUE _self, VALUE klass, VALUE method, VALUE instance) {
38
- direct_bind_cfunc_result result = direct_bind_get_cfunc(klass, SYM2ID(method), true);
39
- if (result.arity != 0) rb_raise(rb_eArgError, "Unexpected arity on cfunc: %d", result.arity);
40
- return ((VALUE (*)(VALUE)) result.func)(instance);
41
- }