ruby-qt6-rice 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.
data/lib/mkmf-rice.rb ADDED
@@ -0,0 +1,101 @@
1
+ # This file is part of [rice](https://github.com/ruby-rice/rice).
2
+ #
3
+ # Copyright (C) 2025 Jason Roelofs <jasongroelofs@gmail.com>
4
+ # Paul Brannan <curlypaul924@gmail.com>,
5
+ # Charlie Savage
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions
9
+ # are met:
10
+ #
11
+ # 1. Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ # 2. Redistributions in binary form must reproduce the above copyright
14
+ # notice, this list of conditions and the following disclaimer in the
15
+ # documentation and/or other materials provided with the distribution.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19
+ # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20
+ # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ require 'mkmf'
29
+
30
+ IS_MSWIN = /mswin/ =~ RUBY_PLATFORM
31
+ IS_MINGW = /mingw/ =~ RUBY_PLATFORM
32
+ IS_DARWIN = RbConfig::CONFIG['host_os'].match?(/darwin/)
33
+
34
+ # The cpp_command is not overwritten in the experimental mkmf C++ support.
35
+ # See https://bugs.ruby-lang.org/issues/17578
36
+ MakeMakefile['C++'].module_eval do
37
+ def cpp_command(outfile, opt="")
38
+ conf = cc_config(opt)
39
+ if $universal and (arch_flag = conf['ARCH_FLAG']) and !arch_flag.empty?
40
+ conf['ARCH_FLAG'] = arch_flag.gsub(/(?:\G|\s)-arch\s+\S+/, '')
41
+ end
42
+ RbConfig::expand("$(CXX) -E #$INCFLAGS #$CPPFLAGS #$CFLAGS #{opt} #{CONFTEST_CXX} #{outfile}",
43
+ conf)
44
+ end
45
+ end
46
+
47
+ # Now pull in the C++ support
48
+ include MakeMakefile['C++']
49
+
50
+ # Rice needs c++17.
51
+ if IS_MSWIN
52
+ $CXXFLAGS += " /std:c++17 /EHs /permissive- /bigobj /utf-8"
53
+ $CPPFLAGS += " -D_ALLOW_KEYWORD_MACROS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE"
54
+ elsif IS_MINGW
55
+ $CXXFLAGS += " -std=c++17 -Wa,-mbig-obj"
56
+ else
57
+ $CXXFLAGS += " -std=c++17"
58
+ end
59
+
60
+ # Rice needs to include its header. Let's setup the include path
61
+ # to make this easy
62
+ path = File.expand_path(File.join(__dir__, '../include'))
63
+
64
+ unless find_header('rice/rice.hpp', path)
65
+ raise("Could not find rice/rice.hpp header")
66
+ end
67
+
68
+ if !IS_DARWIN && !IS_MSWIN && !have_library('stdc++fs')
69
+ have_library('stdc++')
70
+ end
71
+
72
+ # Copied from Ruby FFI bindings - see
73
+ # https://github.com/ffi/ffi/blob/1715332d553d53fae13fd9fcbbd9d2c1982a5c2f/ext/ffi_c/extconf.rb#L7C1-L27C6
74
+ def system_libffi_usable?
75
+ # We need pkg_config or ffi.h
76
+ libffi_ok = pkg_config("libffi") ||
77
+ have_header("ffi.h") ||
78
+ find_header("ffi.h", "/usr/local/include", "/usr/include/ffi",
79
+ "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ffi",
80
+ "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ffi") ||
81
+ (find_header("ffi.h", `xcrun --sdk macosx --show-sdk-path`.strip + "/usr/include/ffi") rescue false)
82
+
83
+ # Ensure we can link to ffi_prep_closure_loc
84
+ libffi_ok &&= have_library("ffi", "ffi_prep_closure_loc", [ "ffi.h" ]) ||
85
+ have_library("libffi", "ffi_prep_closure_loc", [ "ffi.h" ]) ||
86
+ have_library("libffi-8", "ffi_prep_closure_loc", [ "ffi.h" ])
87
+
88
+ if RbConfig::CONFIG['host_os'] =~ /mswin/
89
+ have_library('libffi_convenience')
90
+ have_library('shlwapi')
91
+ end
92
+
93
+ libffi_ok
94
+ end
95
+
96
+ def have_libffi
97
+ # Check for libffi to support C style callacks.
98
+ libffi_usable = system_libffi_usable?
99
+ $CPPFLAGS += " -DHAVE_LIBFFI" if libffi_usable
100
+ libffi_usable
101
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mkmf-rice"
4
+
5
+ def qmake
6
+ return @qmake if @qmake
7
+
8
+ ["qmake6", "qmake"].each do |qmake|
9
+ `#{qmake} -v`
10
+ return @qmake = qmake if $?.success?
11
+ end
12
+ raise "Could not find qmake"
13
+ end
14
+
15
+ def qt_install_headers
16
+ return @qt_install_headers if @qt_install_headers
17
+
18
+ r = ENV["QT_INSTALL_HEADERS"] || ""
19
+ return @qt_install_headers = r unless r == ""
20
+
21
+ r = `#{qmake} -query QT_INSTALL_HEADERS`.strip
22
+ return @qt_install_headers = r unless r == ""
23
+
24
+ raise "Could not determine QT_INSTALL_HEADERS folder"
25
+ end
26
+
27
+ RUBYQT6_CXX_FLAGS = ENV["RUBYQT6_CXX_FLAGS"] || "-Os -fno-fast-math"
28
+ append_cppflags(RUBYQT6_CXX_FLAGS) unless RUBYQT6_CXX_FLAGS.strip == ""
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyQt6
4
+ module Rice
5
+ RICE_RUBYGEM_VERSION = "1.0.0"
6
+ end
7
+ end
data/lib/qt6/rice.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rice/version"
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyQt6
4
+ module RSpec
5
+ class BandoFileParser
6
+ class MissingLine < StandardError
7
+ def initialize(expected, got)
8
+ super("Missing line: expected '#{expected}', but got '#{got}'")
9
+ end
10
+ end
11
+
12
+ def initialize(cppfile, qmod)
13
+ @qmod = qmod
14
+ @bandoes = []
15
+ @lines, @lineno = File.read(cppfile).lines, 0
16
+ end
17
+
18
+ def parse
19
+ parse_bando_variants_declaration
20
+ parse_bando_definitions
21
+ raise MissingLine.new("}", line) unless line == "}"
22
+ @bandoes
23
+ end
24
+
25
+ def parse_bando_variants_declaration
26
+ while line == "" || line.start_with?("#include") || line.start_with?("using ")
27
+ matched = line.match(/^using Bando_(\w+) = Bando(\w+)<(\w+), (.*)>/)
28
+ if matched.nil?
29
+ take_next_line
30
+ next
31
+ end
32
+
33
+ if matched[1] != matched[3]
34
+ expected = "using Bando_#{matched[1]} = Bando#{matched[2]}<#{matched[1]}, ..."
35
+ raise MissingLine.new(expected, line)
36
+ else
37
+ take_next_line
38
+ end
39
+
40
+ @bandoes << Struct.new(:name, :template, :constructor_args).new(matched[1], matched[2], matched[4])
41
+ end
42
+
43
+ @bandoes.each do |bando|
44
+ expected = "Rice::Class rb_mBando_c#{bando.name};"
45
+ if line == expected
46
+ take_next_line
47
+ else
48
+ raise MissingLine.new(expected, line)
49
+ end
50
+ end
51
+
52
+ while line == "" || line.start_with?("void Init_bando_q") || line == "{"
53
+ take_next_line
54
+ end
55
+ end
56
+
57
+ def parse_bando_definitions
58
+ @bandoes.each do |bando|
59
+ parse_bando_definition(bando)
60
+ end
61
+ end
62
+
63
+ def parse_bando_definition(bando)
64
+ name = bando.name
65
+
66
+ expected = "rb_mBando_c#{name} ="
67
+ if line == expected
68
+ take_next_line
69
+ else
70
+ raise MissingLine.new(expected, line)
71
+ end
72
+
73
+ expected = "define_bando_#{bando.template.downcase}_under<Bando_#{name}, #{name}>(rb_mQt6Bando, \"#{name}\")"
74
+ if line == expected
75
+ take_next_line
76
+ else
77
+ raise MissingLine.new(expected, line)
78
+ end
79
+
80
+ expected = ".define_constructor(Constructor<Bando_#{name}, #{bando.constructor_args}>()"
81
+ if line.start_with?(expected)
82
+ take_next_line
83
+ else
84
+ raise MissingLine.new(expected, line)
85
+ end
86
+
87
+ while line == "" || line == "}"
88
+ return if line == "}"
89
+ take_next_line
90
+ end
91
+ end
92
+
93
+ def take_next_line
94
+ @lineno += 1
95
+ end
96
+
97
+ def line
98
+ @lines[@lineno].strip
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyQt6
4
+ module RSpec
5
+ class QlassFileParser
6
+ NESTED_QLASSES = [
7
+ "QMetaObjectConnection",
8
+ "QPainterPathElement",
9
+ "QFormLayoutTakeRowResult",
10
+ "QTextEditExtraSelection"
11
+ ]
12
+
13
+ class MissingLine < StandardError
14
+ def initialize(expected, got)
15
+ super("Missing line: expected '#{expected}', but got '#{got}'")
16
+ end
17
+ end
18
+
19
+ def initialize(cppfile, qmod)
20
+ @qmod = qmod
21
+ @qlasses = []
22
+ @lines, @lineno = File.read(cppfile).lines, 0
23
+ end
24
+
25
+ def parse
26
+ parse_qlass_variants_declaration
27
+ parse_qlass_definitions
28
+ raise MissingLine.new("}", line) unless line == "}"
29
+ @qlasses
30
+ end
31
+
32
+ def parse_qlass_variants_declaration
33
+ while line == "" || line.start_with?("#include") || line == "using namespace Rice;"
34
+ take_next_line
35
+ end
36
+
37
+ while (matched = line.match(/^Rice::Class rb_c(\w+)/))
38
+ @qlasses << Struct.new(:name, :methods, :enums, :flags).new(matched[1], [], [], [])
39
+ take_next_line
40
+ end
41
+
42
+ if @qlasses.length.zero?
43
+ raise MissingLine.new("Rice::Class rb_c...", line)
44
+ end
45
+
46
+ until line.start_with?("void Init_q")
47
+ take_next_line
48
+ end
49
+
50
+ while line == "" || line.start_with?("void Init_q") || line == "{"
51
+ take_next_line
52
+ end
53
+ end
54
+
55
+ def parse_qlass_definitions
56
+ @qlasses.each do |qlass|
57
+ if NESTED_QLASSES.include?(qlass.name)
58
+ take_next_line until line == "" || line == "}"
59
+ next
60
+ end
61
+ parse_qlass_definition(qlass)
62
+ end
63
+ end
64
+
65
+ def parse_qlass_definition(qlass)
66
+ qmod_name = @qmod.name
67
+ name = qlass.name
68
+
69
+ expected = "rb_c#{name} ="
70
+ if line == expected
71
+ take_next_line
72
+ else
73
+ raise MissingLine.new(expected, line)
74
+ end
75
+
76
+ expected = "// RubyQt6::#{qmod_name}::#{name}"
77
+ if line == expected
78
+ take_next_line
79
+ else
80
+ raise MissingLine.new(expected, line)
81
+ end
82
+
83
+ if line.start_with?(/define_class_under<#{name}.*rb_mQt6#{qmod_name}, "#{name}"/)
84
+ take_next_line
85
+ else
86
+ raise MissingLine.new("define_class_under<#{name}...", line)
87
+ end
88
+
89
+ loop do
90
+ case line
91
+ when /\/\/ RubyQt6-Defined Functions/
92
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :rubyqt6_defined_functions)
93
+ when /\/\/ Constructor/
94
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :constructor)
95
+ when /\/\/ Inherits/
96
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :inherits)
97
+ when /\/\/ Public Functions/
98
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :public_functions)
99
+ when /\/\/ Public Slots/
100
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :public_slots)
101
+ when /\/\/ Signals/
102
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :signals)
103
+ when /\/\/ Static Public Members/
104
+ qlass.methods.concat parse_qlass_definition_methods(qlass, :static_public_members)
105
+ when /.define_attr/
106
+ take_next_line
107
+ next
108
+ when "", "}"
109
+ break
110
+ else
111
+ raise MissingLine.new("// Constructor", line)
112
+ end
113
+ end
114
+
115
+ while line == "" || line == "}"
116
+ return if line == "}"
117
+ take_next_line
118
+ end
119
+
120
+ while (matched = line.match(/^Data_Type<(.*)rb_c#{name}(.*) =/))
121
+ break if matched[1].start_with?("QFlags")
122
+ enum = Struct.new(:name).new(matched[2])
123
+ qlass.enums << enum
124
+ parse_qlass_definition_enum(qlass, enum)
125
+ end
126
+
127
+ while line == "" || line == "}"
128
+ return if line == "}"
129
+ take_next_line
130
+ end
131
+
132
+ while (matched = line.match(/^Data_Type<QFlags.*rb_c#{name}(.*) =/))
133
+ flag = Struct.new(:name, :enum_name).new(matched[1], "")
134
+ qlass.flags << flag
135
+ parse_qlass_definition_flag(qlass, flag)
136
+ end
137
+ end
138
+
139
+ def parse_qlass_definition_methods(qlass, type)
140
+ [].tap do |methods|
141
+ loop do
142
+ take_next_line
143
+ break if line.start_with?(/\/\/ (RubyQt6|Constructor|Inherits|Public|Signals|Static)/) || line == "" || line == "}"
144
+
145
+ if line.start_with?("// .define_")
146
+ next
147
+ elsif line.start_with?(".define_")
148
+ methods << parse_qlass_definition_method(line, qlass, type)
149
+ else
150
+ raise MissingLine.new(".define_...", line)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ def parse_qlass_definition_method(line, qlass, type)
157
+ rawline = line
158
+ line = line.sub(".define_constructor", "")
159
+ line = line.sub(".define_singleton_function", "")
160
+ line = line.sub(".define_method", "")
161
+ case line
162
+ when /Constructor<#{qlass.name}.*>/
163
+ rbname = "initialize"
164
+ when /(<.*>)?\("([\w?]+)", \[\]\(/
165
+ rbname = $2
166
+ when /(<.*>)?\("([\w?]+)", [A-Z]/
167
+ rbname = $2
168
+ when /(<.*>)?\("([\w?]+)", &#{qlass.name}::([a-zA-Z0-9_]+)/
169
+ rbname = $2
170
+ cppname = $3
171
+ when /(<.*>)?\("(\W+)", \[\]/
172
+ rbname = $2
173
+ when /(<.*>)?\("(\W+)", [A-Z]/
174
+ rbname = $2
175
+ when /(<.*>)?\("(\W+)", &#{qlass.name}::operator/
176
+ rbname = $2
177
+ else
178
+ raise "Invalid method line: #{line}"
179
+ end
180
+ Struct.new(:type, :rbname, :cppname, :rawline).new(type, rbname, cppname, rawline)
181
+ end
182
+
183
+ def parse_qlass_definition_enum(qlass, enum)
184
+ qmod_name = @qmod.name
185
+ name = qlass.name
186
+ enum_name = enum.name
187
+
188
+ expected = "Data_Type<#{name}::#{enum_name}> rb_c#{name}#{enum_name} ="
189
+ if line == expected
190
+ take_next_line
191
+ else
192
+ raise MissingLine.new(expected, line)
193
+ end
194
+
195
+ expected = "// RubyQt6::#{qmod_name}::#{name}::#{enum_name}"
196
+ if line == expected
197
+ take_next_line
198
+ else
199
+ raise MissingLine.new(expected, line)
200
+ end
201
+
202
+ expected = "define_qenum_under<#{name}::#{enum_name}>(rb_c#{name}, \"#{enum_name}\");"
203
+ if line == expected
204
+ take_next_line
205
+ else
206
+ raise MissingLine.new(expected, line)
207
+ end
208
+
209
+ while line == "" || line.start_with?("define_qenum_value_under")
210
+ take_next_line
211
+ end
212
+ end
213
+
214
+ def parse_qlass_definition_flag(qlass, flag)
215
+ qmod_name = @qmod.name
216
+ name = qlass.name
217
+ flag_name = flag.name
218
+
219
+ expected = /Data_Type<QFlags<#{name}::(.*)>> rb_c#{name}#{flag_name} =/
220
+ if (matched = line.match(expected))
221
+ flag.enum_name = matched[1]
222
+ take_next_line
223
+ else
224
+ raise MissingLine.new(expected, line)
225
+ end
226
+
227
+ expected = "// RubyQt6::#{qmod_name}::#{name}::#{flag_name}"
228
+ if line == expected
229
+ take_next_line
230
+ else
231
+ raise MissingLine.new(expected, line)
232
+ end
233
+
234
+ expected = "define_qflags_under<#{name}::#{flag.enum_name}>(rb_c#{name}, \"#{flag_name}\");"
235
+ if line == expected
236
+ take_next_line
237
+ else
238
+ raise MissingLine.new(expected, line)
239
+ end
240
+
241
+ while line == ""
242
+ take_next_line
243
+ end
244
+ end
245
+
246
+ def take_next_line
247
+ @lineno += 1
248
+ end
249
+
250
+ def line
251
+ @lines[@lineno].strip
252
+ end
253
+ end
254
+ end
255
+ end