ruby-bindgen 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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +25 -0
- data/README.md +68 -0
- data/Rakefile +8 -0
- data/bin/ruby-bindgen +133 -0
- data/docs/architecture.md +238 -0
- data/docs/c/c_bindings.md +65 -0
- data/docs/c/constants.md +21 -0
- data/docs/c/customizing.md +19 -0
- data/docs/c/filtering.md +24 -0
- data/docs/c/getting_started.md +56 -0
- data/docs/c/library_loading.md +53 -0
- data/docs/c/output.md +96 -0
- data/docs/c/types.md +61 -0
- data/docs/c/version_guards.md +105 -0
- data/docs/cmake/cmake_bindings.md +19 -0
- data/docs/cmake/filtering.md +26 -0
- data/docs/cmake/getting_started.md +52 -0
- data/docs/cmake/output.md +110 -0
- data/docs/configuration.md +351 -0
- data/docs/contributing.md +68 -0
- data/docs/cpp/buffers.md +24 -0
- data/docs/cpp/classes.md +139 -0
- data/docs/cpp/cpp_bindings.md +29 -0
- data/docs/cpp/customizing.md +124 -0
- data/docs/cpp/enums.md +42 -0
- data/docs/cpp/filtering.md +35 -0
- data/docs/cpp/getting_started.md +80 -0
- data/docs/cpp/iterators.md +94 -0
- data/docs/cpp/operators.md +170 -0
- data/docs/cpp/output.md +125 -0
- data/docs/cpp/templates.md +114 -0
- data/docs/examples.md +133 -0
- data/docs/index.md +133 -0
- data/docs/prior_art.md +37 -0
- data/docs/troubleshooting.md +243 -0
- data/docs/type_spelling.md +200 -0
- data/docs/updating_bindings.md +55 -0
- data/docs/version_guards.md +69 -0
- data/lib/ruby-bindgen/config.rb +63 -0
- data/lib/ruby-bindgen/generators/cmake/cmake.rb +171 -0
- data/lib/ruby-bindgen/generators/cmake/directory.erb +29 -0
- data/lib/ruby-bindgen/generators/cmake/guard.rb +55 -0
- data/lib/ruby-bindgen/generators/cmake/presets.erb +232 -0
- data/lib/ruby-bindgen/generators/cmake/project.erb +89 -0
- data/lib/ruby-bindgen/generators/ffi/callback.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/constant.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/enum_constant_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/enum_decl.erb +4 -0
- data/lib/ruby-bindgen/generators/ffi/enum_decl_anonymous.erb +4 -0
- data/lib/ruby-bindgen/generators/ffi/ffi.rb +687 -0
- data/lib/ruby-bindgen/generators/ffi/field_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/function.erb +5 -0
- data/lib/ruby-bindgen/generators/ffi/library.erb +39 -0
- data/lib/ruby-bindgen/generators/ffi/project.erb +18 -0
- data/lib/ruby-bindgen/generators/ffi/struct.erb +6 -0
- data/lib/ruby-bindgen/generators/ffi/translation_unit.erb +9 -0
- data/lib/ruby-bindgen/generators/ffi/typedef_decl.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/union.erb +6 -0
- data/lib/ruby-bindgen/generators/ffi/variable.erb +1 -0
- data/lib/ruby-bindgen/generators/ffi/version.erb +9 -0
- data/lib/ruby-bindgen/generators/ffi/version_method.erb +5 -0
- data/lib/ruby-bindgen/generators/generator.rb +52 -0
- data/lib/ruby-bindgen/generators/rice/auto_generated_base_class.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/class.erb +31 -0
- data/lib/ruby-bindgen/generators/rice/class_template.erb +9 -0
- data/lib/ruby-bindgen/generators/rice/class_template_specialization.erb +10 -0
- data/lib/ruby-bindgen/generators/rice/constant.erb +9 -0
- data/lib/ruby-bindgen/generators/rice/constructor.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/conversion_function.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/cxx_iterator_method.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/cxx_method.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/enum_constant_decl.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/enum_decl.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/field_decl.erb +8 -0
- data/lib/ruby-bindgen/generators/rice/function.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/function_pointer.rb +68 -0
- data/lib/ruby-bindgen/generators/rice/incomplete_class.erb +3 -0
- data/lib/ruby-bindgen/generators/rice/iterator_alias.erb +1 -0
- data/lib/ruby-bindgen/generators/rice/iterator_collector.rb +159 -0
- data/lib/ruby-bindgen/generators/rice/namespace.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_binary.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_inspect.erb +6 -0
- data/lib/ruby-bindgen/generators/rice/non_member_operator_unary.erb +4 -0
- data/lib/ruby-bindgen/generators/rice/operator[].erb +4 -0
- data/lib/ruby-bindgen/generators/rice/project.cpp.erb +18 -0
- data/lib/ruby-bindgen/generators/rice/project.hpp.erb +13 -0
- data/lib/ruby-bindgen/generators/rice/reference_qualifier.rb +495 -0
- data/lib/ruby-bindgen/generators/rice/rice.rb +1724 -0
- data/lib/ruby-bindgen/generators/rice/rice_include.hpp.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/signature_builder.rb +230 -0
- data/lib/ruby-bindgen/generators/rice/template_resolver.rb +585 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.cpp.erb +40 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.hpp.erb +7 -0
- data/lib/ruby-bindgen/generators/rice/translation_unit.ipp.erb +3 -0
- data/lib/ruby-bindgen/generators/rice/type_index.rb +117 -0
- data/lib/ruby-bindgen/generators/rice/type_speller.rb +509 -0
- data/lib/ruby-bindgen/generators/rice/union.erb +5 -0
- data/lib/ruby-bindgen/generators/rice/variable.erb +5 -0
- data/lib/ruby-bindgen/inputter.rb +54 -0
- data/lib/ruby-bindgen/name_mapper.rb +65 -0
- data/lib/ruby-bindgen/namer.rb +138 -0
- data/lib/ruby-bindgen/outputter.rb +40 -0
- data/lib/ruby-bindgen/parser.rb +82 -0
- data/lib/ruby-bindgen/refinements/cursor.rb +57 -0
- data/lib/ruby-bindgen/refinements/string.rb +41 -0
- data/lib/ruby-bindgen/symbol_candidates.rb +282 -0
- data/lib/ruby-bindgen/symbol_entry.rb +21 -0
- data/lib/ruby-bindgen/symbols.rb +107 -0
- data/lib/ruby-bindgen/type_pointer_formatter.rb +35 -0
- data/lib/ruby-bindgen/version.rb +3 -0
- data/lib/ruby-bindgen.rb +19 -0
- data/ruby-bindgen.gemspec +52 -0
- metadata +260 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f6c7ab91006aee7a6926a5e8539984035e810270aa1acead7a85cf84421a7509
|
|
4
|
+
data.tar.gz: e6da98262ed5f7c588caf9e20d174613fd90b8bafecdfe02d0490321371d6cf5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a879be6c008ce0067f92e842837d57c8129316431baf059e9c608c4a646ded3e97825ff7738bd43bee559dfe6199a2b9366ecaac32aca1b03d35eaad408138e3
|
|
7
|
+
data.tar.gz: 8466219b1802377f9149f416c5836f0801daed0c0107eeb9b1ad242d7df8d94ce5037c54498756813c6a97bdfe5e2b5966642b9ed1fa80627ba576f332291a1d
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Copyright (C) 2024 Charlie Savage
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions
|
|
7
|
+
are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer.
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
12
|
+
notice, this list of conditions and the following disclaimer in the
|
|
13
|
+
documentation and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
18
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
21
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
22
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
23
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
24
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
25
|
+
SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# ruby-bindgen
|
|
2
|
+
|
|
3
|
+
`ruby-bindgen` generates Ruby bindings from C and C++ header files. It can even generate a CMake build system if needed. It has been battle-tested against large C/C++ libraries such as Proj and OpenCV.
|
|
4
|
+
|
|
5
|
+
Traditionally, wrapping large C and C++ libraries for use in Ruby has been a long, arduous task, especially for large, complex libraries . As a result, many C/C++ libraries are either never exposed to Ruby or their bindings quickly become outdated, especially in scientific and technical domains.
|
|
6
|
+
|
|
7
|
+
For much more information, read the extensive [documentation](https://ruby-rice.github.io/ruby-bindgen/).
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
Create a config file (`rice-bindings.yaml`):
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
project: my_extension
|
|
15
|
+
input: ./include
|
|
16
|
+
output: ./ext/generated
|
|
17
|
+
format: Rice
|
|
18
|
+
|
|
19
|
+
match:
|
|
20
|
+
- "**/*.hpp"
|
|
21
|
+
|
|
22
|
+
clang:
|
|
23
|
+
args:
|
|
24
|
+
- -I./include
|
|
25
|
+
- -std=c++17
|
|
26
|
+
- -xc++
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Generate bindings:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ruby-bindgen rice-bindings.yaml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This produces `.cpp`, `.hpp`, and `.ipp` files ready to compile as a Ruby extension.
|
|
36
|
+
|
|
37
|
+
Relative paths (`./include`, `./ext/generated`, `-I./include`) are resolved relative to the config file's directory, so you can run `ruby-bindgen` from anywhere.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
gem install ruby-bindgen
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- Ruby 3.2+
|
|
48
|
+
- libclang from LLVM/Clang 17 or newer
|
|
49
|
+
|
|
50
|
+
## Documentation
|
|
51
|
+
|
|
52
|
+
Full documentation is at [ruby-rice.github.io/ruby-bindgen](https://ruby-rice.github.io/ruby-bindgen/).
|
|
53
|
+
|
|
54
|
+
- [C++ (Rice) Getting Started](https://ruby-rice.github.io/ruby-bindgen/cpp/getting_started/)
|
|
55
|
+
- [C (FFI) Getting Started](https://ruby-rice.github.io/ruby-bindgen/c/getting_started/)
|
|
56
|
+
- [Configuration Reference](https://ruby-rice.github.io/ruby-bindgen/configuration/)
|
|
57
|
+
|
|
58
|
+
## Real-world bindings
|
|
59
|
+
|
|
60
|
+
These projects use `ruby-bindgen` to generate their Ruby bindings:
|
|
61
|
+
|
|
62
|
+
- [BitmapPlusPlus-ruby](https://github.com/ruby-rice/BitmapPlusPlus-ruby) — C++ bitmap library, fully automated end-to-end example.
|
|
63
|
+
- [proj4rb](https://github.com/cfis/proj4rb) — bindings for the PROJ cartographic projections C library.
|
|
64
|
+
- [opencv-ruby](https://github.com/cfis/opencv-ruby) — bindings for OpenCV (thousands of classes, tens of thousands of methods).
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
BSD-2-Clause
|
data/Rakefile
ADDED
data/bin/ruby-bindgen
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#! /usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'ruby-bindgen/config'
|
|
4
|
+
require 'ruby-bindgen/version'
|
|
5
|
+
|
|
6
|
+
module RubyBindgen
|
|
7
|
+
class Cmd
|
|
8
|
+
attr_reader :config
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
parse_args
|
|
12
|
+
@config = RubyBindgen::Config.new(@config_path)
|
|
13
|
+
require 'ruby-bindgen'
|
|
14
|
+
validate_config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse_args
|
|
18
|
+
if ARGV.empty? || ARGV[0] == '-h' || ARGV[0] == '--help'
|
|
19
|
+
puts usage
|
|
20
|
+
exit(ARGV.empty? ? 1 : 0)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if ARGV[0] == '-v' || ARGV[0] == '--version'
|
|
24
|
+
puts "ruby-bindgen #{RubyBindgen::VERSION}"
|
|
25
|
+
exit 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@config_path = ARGV[0]
|
|
29
|
+
|
|
30
|
+
unless File.exist?(@config_path)
|
|
31
|
+
raise "Config file not found: #{@config_path}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def usage
|
|
36
|
+
<<~USAGE
|
|
37
|
+
ruby-bindgen
|
|
38
|
+
|
|
39
|
+
Usage: ruby-bindgen <config.yaml>
|
|
40
|
+
|
|
41
|
+
Generates Ruby bindings for C and C++ libraries using a YAML configuration file.
|
|
42
|
+
|
|
43
|
+
See documentation for config file format: https://github.com/ruby-rice/ruby-bindgen/blob/main/docs/configuration.md
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
-h, --help Show this help message
|
|
47
|
+
-v, --version Print the ruby-bindgen version and exit
|
|
48
|
+
USAGE
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_config
|
|
52
|
+
raise "Config must specify 'output'" unless @config[:output]
|
|
53
|
+
raise "Config must specify 'format'" unless @config[:format]
|
|
54
|
+
|
|
55
|
+
unless %w[FFI Rice CMake].include?(@config[:format])
|
|
56
|
+
raise "Format must be 'FFI', 'Rice', or 'CMake', got: #{@config[:format]}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Input defaults to output for CMake (scans generated files in output dir)
|
|
60
|
+
@config[:input] ||= @config[:output]
|
|
61
|
+
|
|
62
|
+
unless File.directory?(@config[:input])
|
|
63
|
+
raise "Input path must be a directory: #{@config[:input]}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
unless File.directory?(@config[:output])
|
|
67
|
+
raise "Output path must be a directory: #{@config[:output]}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Validate project name (if provided). Hyphens are allowed and normalized to underscores.
|
|
71
|
+
if @config[:project] && !@config[:project].match?(/\A[A-Za-z_][A-Za-z0-9_-]*\z/)
|
|
72
|
+
raise "Project name must be a valid C/C++ identifier (hyphens allowed): #{@config[:project]}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# User-supplied Rice include header must exist under the output directory
|
|
76
|
+
# at config time. If it is missing, every generated -rb.hpp file would
|
|
77
|
+
# emit `#include "..."` for a nonexistent header and the C++ build would
|
|
78
|
+
# fail with a confusing 'file not found' error far from the cause.
|
|
79
|
+
if @config[:format] == "Rice" && @config[:include]
|
|
80
|
+
include_path = File.join(@config[:output], @config[:include])
|
|
81
|
+
unless File.exist?(include_path)
|
|
82
|
+
raise "Rice include header not found: #{include_path}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Validate libclang shared library if specified.
|
|
87
|
+
if @config[:libclang] && !File.exist?(@config[:libclang])
|
|
88
|
+
raise "libclang library not found: #{@config[:libclang]}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# clang/clang-cl args must be an array, not a string.
|
|
92
|
+
if @config[:clang_args] && !@config[:clang_args].is_a?(Array)
|
|
93
|
+
raise "clang args must be a YAML list, got: #{@config[:clang_args].class}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def run
|
|
98
|
+
input = @config[:input]
|
|
99
|
+
default_match = @config[:format] == "CMake" ? ["**/*-rb.cpp"] : ["**/*.{h,hpp}"]
|
|
100
|
+
match_patterns = @config[:match] || default_match
|
|
101
|
+
skip_patterns = @config[:skip] || []
|
|
102
|
+
|
|
103
|
+
inputter = RubyBindgen::Inputter.new(input, match_patterns, skip_patterns)
|
|
104
|
+
outputter = RubyBindgen::Outputter.new(@config[:output])
|
|
105
|
+
|
|
106
|
+
generator_klass = RubyBindgen::Generators.const_get(@config[:format])
|
|
107
|
+
generator = generator_klass.new(inputter, outputter, @config)
|
|
108
|
+
generator.generate
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
debug = ENV['BINDGEN_DEBUG']
|
|
114
|
+
|
|
115
|
+
begin
|
|
116
|
+
cmd = RubyBindgen::Cmd.new
|
|
117
|
+
rescue => e
|
|
118
|
+
# Setup-time errors (missing/invalid config, bad arguments). Treat as user
|
|
119
|
+
# errors and skip the backtrace unless BINDGEN_DEBUG is set.
|
|
120
|
+
STDERR.puts "Error: #{e.message}"
|
|
121
|
+
STDERR.puts e.backtrace if debug
|
|
122
|
+
exit 1
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
begin
|
|
126
|
+
cmd.run
|
|
127
|
+
rescue => e
|
|
128
|
+
# Runtime errors during generation. Print the backtrace because these
|
|
129
|
+
# usually indicate a bug worth reporting.
|
|
130
|
+
STDERR.puts "Error: #{e.message}"
|
|
131
|
+
STDERR.puts e.backtrace
|
|
132
|
+
exit 1
|
|
133
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`ruby-bindgen` uses [libclang](https://clang.llvm.org/doxygen/group__CINDEX.html), via [ffi-clang](https://github.com/ioquatix/ffi-clang), to parse C and C++ header files.
|
|
4
|
+
|
|
5
|
+
Libclang represents the [Abstract Syntax Tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree) as a hierarchy of cursors. Each cursor is a node representing a declaration, statement, or expression. For example, parsing this header:
|
|
6
|
+
|
|
7
|
+
```cpp
|
|
8
|
+
namespace cv {
|
|
9
|
+
class Mat {
|
|
10
|
+
Mat(int rows, int cols, int type);
|
|
11
|
+
int rows;
|
|
12
|
+
bool empty() const;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Produces a cursor tree like:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
namespace (cv)
|
|
21
|
+
└── class_decl (Mat)
|
|
22
|
+
├── constructor (Mat)
|
|
23
|
+
│ ├── parm_decl (rows)
|
|
24
|
+
│ ├── parm_decl (cols)
|
|
25
|
+
│ └── parm_decl (type)
|
|
26
|
+
├── field_decl (rows)
|
|
27
|
+
└── cxx_method (empty)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For C and C++ bindings, `ruby-bindgen` uses a [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern)-style traversal to walk this tree. Each cursor kind is dispatched to a corresponding `visit_*` method (e.g., `visit_class_decl`, `visit_cxx_method`), which generates binding code via ERB templates.
|
|
31
|
+
|
|
32
|
+
``` mermaid
|
|
33
|
+
flowchart TD
|
|
34
|
+
subgraph Input
|
|
35
|
+
H["C/C++ headers"]
|
|
36
|
+
Y["bindings.yaml"]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
subgraph Parsing
|
|
40
|
+
FC["ffi-clang"]
|
|
41
|
+
AST["AST"]
|
|
42
|
+
FC --> AST
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
subgraph "Code Generation"
|
|
46
|
+
V1["Generator"]
|
|
47
|
+
ERB1["ERB templates"]
|
|
48
|
+
V1 --> ERB1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
subgraph Output
|
|
52
|
+
F["Rice/FFI files"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
H --> FC
|
|
56
|
+
Y --> V1
|
|
57
|
+
AST --> V1
|
|
58
|
+
ERB1 --> F
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For CMake bindings, `ruby-bindgen` runs as a second pass, scanning the output directory for previously generated `*-rb.cpp` files.
|
|
62
|
+
|
|
63
|
+
``` mermaid
|
|
64
|
+
flowchart TD
|
|
65
|
+
subgraph Input
|
|
66
|
+
Y["cmake-bindings.yaml"]
|
|
67
|
+
S["*-rb.cpp"]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subgraph "Code Generation"
|
|
71
|
+
V2["CMake generator"]
|
|
72
|
+
ERB2["ERB templates"]
|
|
73
|
+
V2 --> ERB2
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
subgraph Output
|
|
77
|
+
F["CMakeLists.txt / CMakePresets.json"]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Y --> V2
|
|
81
|
+
S --> V2
|
|
82
|
+
ERB2 --> F
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Source Layout
|
|
86
|
+
|
|
87
|
+
The [key classes](#key-classes) live under `lib/ruby-bindgen/`. Each output format (Rice, FFI, CMake) has its own directory under `generators/` containing both the generator implementation and its ERB templates.
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
lib/ruby-bindgen/
|
|
91
|
+
├── config.rb # YAML config loading
|
|
92
|
+
├── inputter.rb # Header file discovery
|
|
93
|
+
├── outputter.rb # File writing with cleanup
|
|
94
|
+
├── parser.rb # ffi-clang AST parsing
|
|
95
|
+
├── name_mapper.rb # Exact/regex name remapping
|
|
96
|
+
├── namer.rb # C++ → Ruby name conversion
|
|
97
|
+
├── symbols.rb # skip / version / override matching
|
|
98
|
+
├── symbol_entry.rb # Per-symbol skip/version/override record
|
|
99
|
+
├── symbol_candidates.rb # Candidate name generation for lookup
|
|
100
|
+
├── type_pointer_formatter.rb # Pointer type formatting helpers
|
|
101
|
+
├── version.rb
|
|
102
|
+
├── refinements/ # Extensions to ffi-clang and stdlib classes
|
|
103
|
+
│ ├── cursor.rb # Cursor: ruby_name, cruby_name, anonymous_definer, namer
|
|
104
|
+
│ └── string.rb # String: camelize, underscore, upcase_first
|
|
105
|
+
└── generators/
|
|
106
|
+
├── generator.rb # Base class shared by generators
|
|
107
|
+
├── rice/ # C++ Rice binding generator
|
|
108
|
+
│ ├── rice.rb # Main C++ generator (AST visitor); the helpers
|
|
109
|
+
│ │ # below are nested under Rice (e.g.
|
|
110
|
+
│ │ # RubyBindgen::Generators::Rice::TypeSpeller).
|
|
111
|
+
│ ├── type_speller.rb # Reconstructs qualified C++ type names
|
|
112
|
+
│ ├── type_index.rb # Index of typedefs and template instantiations
|
|
113
|
+
│ ├── signature_builder.rb # Builds Rice method signatures
|
|
114
|
+
│ ├── template_resolver.rb # Class template resolution
|
|
115
|
+
│ ├── iterator_collector.rb# Detects begin/end iterator pairs
|
|
116
|
+
│ ├── function_pointer.rb # Function pointer typedef handling
|
|
117
|
+
│ ├── reference_qualifier.rb# Reference / const qualifiers
|
|
118
|
+
│ └── *.erb # ERB templates
|
|
119
|
+
├── ffi/ # C FFI binding generator
|
|
120
|
+
│ ├── ffi.rb # Main C generator
|
|
121
|
+
│ └── *.erb # ERB templates
|
|
122
|
+
└── cmake/ # CMake build file generator
|
|
123
|
+
├── cmake.rb # Main CMake generator
|
|
124
|
+
├── guard.rb # Per-target build guards
|
|
125
|
+
└── *.erb # ERB templates
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Most generator methods delegate to ERB templates for code generation. Each template receives the current cursor and any generator state as local variables, and outputs a string of generated code.
|
|
129
|
+
|
|
130
|
+
For example, the Rice generator's `cxx_method.erb` template generates a `define_method` call:
|
|
131
|
+
|
|
132
|
+
```cpp
|
|
133
|
+
define_method<%= signature %>("<%= name %>", &<%= qualified_parent %>::<%= cursor.spelling %>,
|
|
134
|
+
<%= all_args.join(", ") %>)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Key Classes
|
|
138
|
+
|
|
139
|
+
### Config
|
|
140
|
+
|
|
141
|
+
The `Config` class loads the YAML configuration file and resolves platform-specific settings. It detects whether to use `clang:` or `clang-cl:` based on `RUBY_PLATFORM` (`mswin` uses `clang-cl:`; Linux, macOS, and MinGW use `clang:`), resolves relative paths against the config file's directory, and provides hash-like access to all configuration values.
|
|
142
|
+
|
|
143
|
+
### Inputter
|
|
144
|
+
|
|
145
|
+
The `Inputter` class discovers header files to process. Given a base directory and glob patterns from the config (`match:` and `skip:`), it iterates over matching files and yields both absolute and relative paths.
|
|
146
|
+
|
|
147
|
+
### Parser
|
|
148
|
+
|
|
149
|
+
The `Parser` class wraps ffi-clang's `Index` and drives the processing loop:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
def generate(visitor)
|
|
153
|
+
visitor.visit_start
|
|
154
|
+
|
|
155
|
+
inputter.each do |path, relative_path|
|
|
156
|
+
translation_unit = @index.parse_translation_unit(path, clang_args, [],
|
|
157
|
+
[:detailed_preprocessing_record, :skip_function_bodies])
|
|
158
|
+
visitor.visit_translation_unit(translation_unit, path, relative_path)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
visitor.visit_end
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
For each header file, it calls `parse_translation_unit`, which returns a translation unit object. Visitors access the root cursor via `translation_unit.cursor`.
|
|
166
|
+
|
|
167
|
+
Parse options include `:skip_function_bodies` (we only need declarations, not implementations) and `:detailed_preprocessing_record` (to see preprocessor directives).
|
|
168
|
+
The parser also checks diagnostics after each translation unit and raises on fatal/error diagnostics.
|
|
169
|
+
|
|
170
|
+
### Outputter
|
|
171
|
+
|
|
172
|
+
The `Outputter` class writes generated files to the output directory. It tracks all written paths and applies whitespace cleanup (removing excessive blank lines and blank lines before closing braces) to keep the output tidy.
|
|
173
|
+
|
|
174
|
+
### Namer
|
|
175
|
+
|
|
176
|
+
The `Namer` class converts C++ names to Ruby conventions:
|
|
177
|
+
|
|
178
|
+
- `CamelCase` class names stay as-is (Ruby classes are CamelCase)
|
|
179
|
+
- `camelCase` and `PascalCase` method names become `snake_case`
|
|
180
|
+
- `isFoo()` / `is_foo()` become `foo?` (any `bool`-returning method whose name starts with `is`, plus any zero-arg `bool`-returning method, gets the `?` suffix)
|
|
181
|
+
- C++ operators map to Ruby operators (`operator+` → `+`, `operator==` → `==`)
|
|
182
|
+
- `operator[]` maps to both `[]` and `[]=` (if the return type is a reference)
|
|
183
|
+
- `operator()` maps to `call`
|
|
184
|
+
- Conversion operators like `operator int()` map to `to_i`, `operator string()` to `to_s`
|
|
185
|
+
- C variable names for Rice classes use the `rb_c` prefix (e.g., `rb_cCvMat`)
|
|
186
|
+
|
|
187
|
+
## Extensions to ffi-clang and stdlib
|
|
188
|
+
|
|
189
|
+
The `lib/ruby-bindgen/refinements/` directory holds open monkey-patches that
|
|
190
|
+
extend ffi-clang and Ruby's `String`. They are loaded globally — not actual
|
|
191
|
+
Ruby refinements — so any code that loads `ruby-bindgen` sees the additions.
|
|
192
|
+
|
|
193
|
+
- **Cursor** (`refinements/cursor.rb`) — adds `ruby_name`, `cruby_name`,
|
|
194
|
+
`anonymous_definer`, and a class-level `namer` accessor that the generators
|
|
195
|
+
set during `generate`.
|
|
196
|
+
- **String** (`refinements/string.rb`) — adds `camelize`, `underscore`, and
|
|
197
|
+
`upcase_first` for name conversion.
|
|
198
|
+
|
|
199
|
+
Reconstructing qualified C++ type names is handled by the `TypeSpeller` class
|
|
200
|
+
(`lib/ruby-bindgen/generators/rice/type_speller.rb`), which calls
|
|
201
|
+
`FFI::Clang::Type#fully_qualified_name` (provided directly by ffi-clang ≥
|
|
202
|
+
0.16) and post-processes the result. See [Type Spelling](type_spelling.md).
|
|
203
|
+
|
|
204
|
+
## Rice Generator Details
|
|
205
|
+
|
|
206
|
+
The Rice generator is the largest part of the codebase because C++ has the most features to handle. Some notable aspects:
|
|
207
|
+
|
|
208
|
+
### Traversal
|
|
209
|
+
|
|
210
|
+
The generator traverses the AST recursively. Each cursor kind is dispatched to a `visit_*` method (e.g., `visit_class_decl`, `visit_cxx_method`) which checks whether the cursor should be skipped and, if not, renders an ERB template to generate the binding code.
|
|
211
|
+
|
|
212
|
+
### Filtering
|
|
213
|
+
|
|
214
|
+
Before generating code for a cursor, the visitor applies several filters. See [filtering](cpp/filtering.md) for details.
|
|
215
|
+
|
|
216
|
+
### Template Handling
|
|
217
|
+
|
|
218
|
+
C++ class templates require special treatment. See [templates](cpp/templates.md) for details.
|
|
219
|
+
|
|
220
|
+
### Type Spelling
|
|
221
|
+
|
|
222
|
+
Reconstructing correct C++ type names from libclang's type information is one of the trickiest parts of the codebase. The `TypeSpeller` class (`lib/ruby-bindgen/generators/rice/type_speller.rb`) handles:
|
|
223
|
+
|
|
224
|
+
- Namespace qualification (`cv::Mat` not `Mat`)
|
|
225
|
+
- Template argument qualification (`std::vector<cv::Point>` not `std::vector<Point>`)
|
|
226
|
+
- Typedef resolution (knowing when to use the alias vs the underlying type)
|
|
227
|
+
- Dependent types in templates (adding `typename` where required)
|
|
228
|
+
- Elaborated types (`enum Foo` vs `Foo`)
|
|
229
|
+
|
|
230
|
+
### Default Arguments
|
|
231
|
+
|
|
232
|
+
Libclang provides limited information about default argument values. `ruby-bindgen` extracts default values from the source text and wraps them in `static_cast` with fully qualified types to ensure they compile in the generated context:
|
|
233
|
+
|
|
234
|
+
```cpp
|
|
235
|
+
// Original: void resize(int size, const Scalar& value = Scalar())
|
|
236
|
+
// Generated:
|
|
237
|
+
Arg("value") = static_cast<const cv::Scalar&>(cv::Scalar())
|
|
238
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# C Bindings
|
|
2
|
+
|
|
3
|
+
`ruby-bindgen` generates Ruby files that use the [FFI](https://github.com/ffi/ffi) gem to call C library functions. FFI allows Ruby to directly load and call functions from C shared libraries without writing a C extension. This means:
|
|
4
|
+
|
|
5
|
+
- **No compilation step** - Ruby loads the library at runtime
|
|
6
|
+
- **Easier distribution** - No need to compile native code for each platform
|
|
7
|
+
- **Simpler development** - No C code to write or debug
|
|
8
|
+
|
|
9
|
+
If a library provides a C API then use it!
|
|
10
|
+
|
|
11
|
+
## Supported Features
|
|
12
|
+
|
|
13
|
+
`ruby-bindgen` supports the following FFI features:
|
|
14
|
+
|
|
15
|
+
- [Functions](https://github.com/ffi/ffi/wiki/Basic-Usage) via `attach_function`, including variadic functions (`:varargs`). Functions taking `va_list` parameters are skipped since `va_list` cannot be constructed from Ruby.
|
|
16
|
+
- [Structs](https://github.com/ffi/ffi/wiki/Structs) map to `FFI::Struct`
|
|
17
|
+
- Unions map to `FFI::Union`
|
|
18
|
+
- [Enums](https://github.com/ffi/ffi/wiki/Enums) map to FFI enum types
|
|
19
|
+
- [Callbacks](https://github.com/ffi/ffi/wiki/Callbacks) for function pointer types
|
|
20
|
+
- [Typedefs](https://github.com/ffi/ffi/wiki/Types) are preserved
|
|
21
|
+
- Forward declarations map to opaque pointer types
|
|
22
|
+
- Global variables via [`attach_variable`](https://www.rubydoc.info/gems/ffi/FFI/Library#attach_variable-instance_method)
|
|
23
|
+
- Constants from `const` variables (see [Constants and Macros](constants.md))
|
|
24
|
+
|
|
25
|
+
## Getting Started
|
|
26
|
+
|
|
27
|
+
See [Getting Started](getting_started.md) for a step-by-step guide to creating your first FFI bindings.
|
|
28
|
+
|
|
29
|
+
## Output
|
|
30
|
+
|
|
31
|
+
See [FFI Output](output.md) for details on the generated files.
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
The test suite includes bindings generated from some popular C libraries:
|
|
36
|
+
|
|
37
|
+
| Library | Description |
|
|
38
|
+
|-----------------|-----------------------------------|
|
|
39
|
+
| [PROJ](https://proj.org/) | Coordinate transformation library |
|
|
40
|
+
| [SQLite](https://sqlite.org/) | Database engine |
|
|
41
|
+
| [libclang](https://clang.llvm.org/) | C/C++ parsing library |
|
|
42
|
+
|
|
43
|
+
See [test/headers/c](https://github.com/ruby-rice/ruby-bindgen/tree/main/test/headers/c) for the input headers and [test/bindings/c](https://github.com/ruby-rice/ruby-bindgen/tree/main/test/bindings/c) for the generated Ruby bindings.
|
|
44
|
+
|
|
45
|
+
## Ruby Wrapper Classes
|
|
46
|
+
|
|
47
|
+
Since C is procedural rather than object-oriented, you may wish to wrap the generated FFI bindings in Ruby classes to provide a more idiomatic API:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
require_relative 'generated/mylib_ffi'
|
|
51
|
+
|
|
52
|
+
class MyLibWrapper
|
|
53
|
+
def initialize
|
|
54
|
+
@handle = Mylib.create_handle()
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def process(data)
|
|
58
|
+
Mylib.process_data(@handle, data)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def close
|
|
62
|
+
Mylib.destroy_handle(@handle)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
data/docs/c/constants.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Constants and Macros
|
|
2
|
+
|
|
3
|
+
`ruby-bindgen` generates Ruby constants from top-level `const` variables with literal initializers:
|
|
4
|
+
|
|
5
|
+
```c
|
|
6
|
+
const int CONST_INT = 10;
|
|
7
|
+
static const int STATIC_CONST_INT = 20;
|
|
8
|
+
const double CONST_DOUBLE = 3.14;
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Generates:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
CONST_INT = 10
|
|
15
|
+
STATIC_CONST_INT = 20
|
|
16
|
+
CONST_DOUBLE = 3.14
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Macros
|
|
20
|
+
|
|
21
|
+
Macros are not added to bindings because their values are baked in at generation time and thus not read at runtime. This easily causes confusion. For example, a version macro like `PROJ_VERSION_MAJOR` would reflect whichever library version was used to generate bindings, not the version actually being used on your machine.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Customizing
|
|
2
|
+
|
|
3
|
+
`ruby-bindgen` can customize the generated bindings in several ways:
|
|
4
|
+
|
|
5
|
+
- [`symbols: skip:`](../configuration.md#skip) — exclude specific functions, structs, enums, or typedefs by name or regex pattern
|
|
6
|
+
- [`symbols: overrides:`](../configuration.md#overrides-ffi-only) — replace the generated signature for specific functions when the heuristics pick the wrong FFI type
|
|
7
|
+
- [`export_macros`](../configuration.md#export-macros) — only include functions marked with specific visibility macros
|
|
8
|
+
- [`rename_types`](../configuration.md#name-mappings) — override generated Ruby module/class names
|
|
9
|
+
- [`rename_methods`](../configuration.md#name-mappings) — override generated Ruby method names
|
|
10
|
+
|
|
11
|
+
## Module Name
|
|
12
|
+
|
|
13
|
+
By default, the generated Ruby module is named after the header file (e.g., `proj.h` → `module Proj`). Use the [`module`](../configuration.md#c-ffi-options) option to override this, including nested modules:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
module: Proj::Api
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This generates properly nested `module Proj` / `module Api` with correct indentation.
|
data/docs/c/filtering.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Filtering
|
|
2
|
+
|
|
3
|
+
`ruby-bindgen` provides several mechanisms to control which symbols are included in the generated bindings.
|
|
4
|
+
|
|
5
|
+
## Export Macros
|
|
6
|
+
|
|
7
|
+
Many libraries use macros to control which functions are exported. `ruby-bindgen` can honor these macros so that only exported functions are included in the generated bindings. See [`export_macros`](../configuration.md#export-macros) for details.
|
|
8
|
+
|
|
9
|
+
## Skipping Symbols
|
|
10
|
+
|
|
11
|
+
Sometimes you need to exclude specific symbols from the generated bindings — for example, internal APIs, symbols that cause linker errors, or platform-specific functions. `ruby-bindgen` supports skipping by name, qualified name, or regex pattern. See [`symbols.skip`](../configuration.md#skip) for details.
|
|
12
|
+
|
|
13
|
+
## Version Guards
|
|
14
|
+
|
|
15
|
+
When a library evolves across versions, some symbols are only available in newer releases. `ruby-bindgen` can wrap these symbols in version guards so the same bindings compile against multiple library versions. See [`symbols.versions`](../configuration.md#versions) for details.
|
|
16
|
+
|
|
17
|
+
## Automatic Skipping
|
|
18
|
+
|
|
19
|
+
The following are automatically skipped:
|
|
20
|
+
|
|
21
|
+
- **Deprecated**: Functions marked with `__attribute__((deprecated))` or `[[deprecated]]`
|
|
22
|
+
- **va_list**: Functions taking `va_list` parameters (cannot be constructed from Ruby; use the variadic `...` version instead)
|
|
23
|
+
- **Private/Protected**: Non-public members
|
|
24
|
+
- **System headers**: Declarations from system include paths
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Getting Started with FFI Bindings
|
|
2
|
+
|
|
3
|
+
This guide walks you through generating Ruby FFI bindings for a C library.
|
|
4
|
+
|
|
5
|
+
## 1. Create a configuration file
|
|
6
|
+
|
|
7
|
+
Create a file named `ffi-bindings.yaml`:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
project: mylib
|
|
11
|
+
input: ./include
|
|
12
|
+
output: ./lib/generated
|
|
13
|
+
format: FFI
|
|
14
|
+
|
|
15
|
+
match:
|
|
16
|
+
- "**/*.h"
|
|
17
|
+
|
|
18
|
+
library_names:
|
|
19
|
+
- mylib
|
|
20
|
+
|
|
21
|
+
clang:
|
|
22
|
+
args:
|
|
23
|
+
- -I./include
|
|
24
|
+
- -xc
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Key options:
|
|
28
|
+
|
|
29
|
+
- `project` — name used for the project file (`mylib_ffi.rb`) and the Ruby module
|
|
30
|
+
- `input` — directory containing header files
|
|
31
|
+
- `output` — where generated Ruby files are written
|
|
32
|
+
- `library_names` — shared library names to load at runtime (e.g., `mylib` for `libmylib.so`)
|
|
33
|
+
- `match` — glob patterns selecting which headers to process
|
|
34
|
+
- `clang.args` — compiler arguments; `-xc` tells clang to parse as C
|
|
35
|
+
|
|
36
|
+
Paths are relative to the config file's directory, not the working directory.
|
|
37
|
+
|
|
38
|
+
See [Configuration](../configuration.md) for all options, including [library versioning](../configuration.md#c-ffi-options), [symbol filtering](../configuration.md#symbols), and [version guards](../configuration.md#versions).
|
|
39
|
+
|
|
40
|
+
## 2. Generate bindings
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ruby-bindgen ffi-bindings.yaml
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This produces a project file, one Ruby file per matched header, and optionally a version stub file. See [FFI Output](output.md) for details.
|
|
47
|
+
|
|
48
|
+
## 3. Use the bindings
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
require_relative 'lib/generated/mylib_ffi'
|
|
52
|
+
|
|
53
|
+
result = Mylib.my_function(42, "hello")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The project file (`mylib_ffi.rb`) loads the native library and requires all content files. You only need to require the project file.
|