mini_racer 0.3.1 → 0.6.4

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.
@@ -0,0 +1,13 @@
1
+ require 'mkmf'
2
+
3
+ if RUBY_ENGINE == "truffleruby"
4
+ File.write("Makefile", dummy_makefile($srcdir).join(""))
5
+ return
6
+ end
7
+
8
+ extension_name = 'mini_racer_loader'
9
+ dir_config extension_name
10
+
11
+ $CXXFLAGS += " -fvisibility=hidden "
12
+
13
+ create_makefile extension_name
@@ -0,0 +1,123 @@
1
+ #include <ruby.h>
2
+ #include <dlfcn.h>
3
+ #include <string.h>
4
+ #include <stdint.h>
5
+ #include <stdlib.h>
6
+
7
+ // Load a Ruby extension like Ruby does, only with flags that:
8
+ // a) hide symbols from other extensions (RTLD_LOCAL)
9
+ // b) bind symbols tightly (RTLD_DEEPBIND, when available)
10
+
11
+ void Init_mini_racer_loader(void);
12
+
13
+ static void *_dln_load(const char *file);
14
+
15
+ static VALUE _load_shared_lib(VALUE self, volatile VALUE fname)
16
+ {
17
+ (void) self;
18
+
19
+ // check that path is not tainted
20
+ SafeStringValue(fname);
21
+
22
+ FilePathValue(fname);
23
+ VALUE path = rb_str_encode_ospath(fname);
24
+
25
+ char *loc = StringValueCStr(path);
26
+ void *handle = _dln_load(loc);
27
+
28
+ return handle ? Qtrue : Qfalse;
29
+ }
30
+
31
+ // adapted from Ruby's dln.c
32
+ #define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'})
33
+ #define INIT_FUNCNAME(buf, file) do { \
34
+ const char *base = (file); \
35
+ const size_t flen = _init_funcname(&base); \
36
+ const size_t plen = sizeof(INIT_FUNC_PREFIX); \
37
+ char *const tmp = ALLOCA_N(char, plen + flen + 1); \
38
+ memcpy(tmp, INIT_FUNC_PREFIX, plen); \
39
+ memcpy(tmp+plen, base, flen); \
40
+ tmp[plen+flen] = '\0'; \
41
+ *(buf) = tmp; \
42
+ } while(0)
43
+
44
+ // adapted from Ruby's dln.c
45
+ static size_t _init_funcname(const char **file)
46
+ {
47
+ const char *p = *file,
48
+ *base,
49
+ *dot = NULL;
50
+
51
+ for (base = p; *p; p++) { /* Find position of last '/' */
52
+ if (*p == '.' && !dot) {
53
+ dot = p;
54
+ }
55
+ if (*p == '/') {
56
+ base = p + 1;
57
+ dot = NULL;
58
+ }
59
+ }
60
+ *file = base;
61
+ return (uintptr_t) ((dot ? dot : p) - base);
62
+ }
63
+
64
+ // adapted from Ruby's dln.c
65
+ static void *_dln_load(const char *file)
66
+ {
67
+ char *buf;
68
+ const char *error;
69
+ #define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error))
70
+
71
+ void *handle;
72
+ void (*init_fct)(void);
73
+
74
+ INIT_FUNCNAME(&buf, file);
75
+
76
+ #ifndef RTLD_DEEPBIND
77
+ # define RTLD_DEEPBIND 0
78
+ #endif
79
+ /* Load file */
80
+ if ((handle = dlopen(file, RTLD_LAZY|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) {
81
+ DLN_ERROR();
82
+ goto failed;
83
+ }
84
+ #if defined(RUBY_EXPORT)
85
+ {
86
+ static const char incompatible[] = "incompatible library version";
87
+ void *ex = dlsym(handle, "ruby_xmalloc");
88
+ if (ex && ex != (void *) &ruby_xmalloc) {
89
+
90
+ # if defined __APPLE__
91
+ /* dlclose() segfaults */
92
+ rb_fatal("%s - %s", incompatible, file);
93
+ # else
94
+ dlclose(handle);
95
+ error = incompatible;
96
+ goto failed;
97
+ #endif
98
+ }
99
+ }
100
+ # endif
101
+
102
+ init_fct = (void (*)(void)) dlsym(handle, buf);
103
+ if (init_fct == NULL) {
104
+ error = DLN_ERROR();
105
+ dlclose(handle);
106
+ goto failed;
107
+ }
108
+
109
+ /* Call the init code */
110
+ (*init_fct)();
111
+
112
+ return handle;
113
+
114
+ failed:
115
+ rb_raise(rb_eLoadError, "%s", error);
116
+ }
117
+
118
+ __attribute__((visibility("default"))) void Init_mini_racer_loader()
119
+ {
120
+ VALUE mMiniRacer = rb_define_module("MiniRacer");
121
+ VALUE mLoader = rb_define_module_under(mMiniRacer, "Loader");
122
+ rb_define_singleton_method(mLoader, "load", _load_shared_lib, 1);
123
+ }
@@ -0,0 +1,355 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniRacer
4
+
5
+ class Context
6
+
7
+ class ExternalFunction
8
+ private
9
+
10
+ def notify_v8
11
+ name = @name.encode(::Encoding::UTF_8)
12
+ wrapped = lambda do |*args|
13
+ converted = @parent.send(:convert_js_to_ruby, args)
14
+ begin
15
+ result = @callback.call(*converted)
16
+ rescue Polyglot::ForeignException => e
17
+ e = RuntimeError.new(e.message)
18
+ e.set_backtrace(e.backtrace)
19
+ @parent.instance_variable_set(:@current_exception, e)
20
+ raise e
21
+ rescue => e
22
+ @parent.instance_variable_set(:@current_exception, e)
23
+ raise e
24
+ end
25
+ @parent.send(:convert_ruby_to_js, result)
26
+ end
27
+
28
+ if @parent_object.nil?
29
+ # set global name to proc
30
+ result = @parent.eval_in_context('this')
31
+ result[name] = wrapped
32
+ else
33
+ parent_object_eval = @parent_object_eval.encode(::Encoding::UTF_8)
34
+ begin
35
+ result = @parent.eval_in_context(parent_object_eval)
36
+ rescue Polyglot::ForeignException, StandardError => e
37
+ raise ParseError, "Was expecting #{@parent_object} to be an object", e.backtrace
38
+ end
39
+ result[name] = wrapped
40
+ # set evaluated object results name to proc
41
+ end
42
+ end
43
+ end
44
+
45
+ def heap_stats
46
+ {
47
+ total_physical_size: 0,
48
+ total_heap_size_executable: 0,
49
+ total_heap_size: 0,
50
+ used_heap_size: 0,
51
+ heap_size_limit: 0,
52
+ }
53
+ end
54
+
55
+ def stop
56
+ if @entered
57
+ @context.stop
58
+ @stopped = true
59
+ stop_attached
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ @context_initialized = false
66
+ @use_strict = false
67
+
68
+ def init_unsafe(isolate, snapshot)
69
+ unless defined?(Polyglot::InnerContext)
70
+ raise "TruffleRuby #{RUBY_ENGINE_VERSION} does not have support for inner contexts, use a more recent version"
71
+ end
72
+
73
+ unless Polyglot.languages.include? "js"
74
+ raise "The language 'js' is not available, you likely need to `export TRUFFLERUBYOPT='--jvm --polyglot'`\n" \
75
+ "You also need to install the 'js' component with 'gu install js' on GraalVM 22.2+\n" \
76
+ "Note that you need TruffleRuby+GraalVM and not just the TruffleRuby standalone to use MiniRacer"
77
+ end
78
+
79
+ @context = Polyglot::InnerContext.new(on_cancelled: -> {
80
+ raise ScriptTerminatedError, 'JavaScript was terminated (either by timeout or explicitly)'
81
+ })
82
+ Context.instance_variable_set(:@context_initialized, true)
83
+ @js_object = @context.eval('js', 'Object')
84
+ @isolate_mutex = Mutex.new
85
+ @stopped = false
86
+ @entered = false
87
+ @has_entered = false
88
+ @current_exception = nil
89
+ if isolate && snapshot
90
+ isolate.instance_variable_set(:@snapshot, snapshot)
91
+ end
92
+ if snapshot
93
+ @snapshot = snapshot
94
+ elsif isolate
95
+ @snapshot = isolate.instance_variable_get(:@snapshot)
96
+ else
97
+ @snapshot = nil
98
+ end
99
+ @is_object_or_array_func, @is_time_func, @js_date_to_time_func, @is_symbol_func, @js_symbol_to_symbol_func, @js_new_date_func, @js_new_array_func = eval_in_context <<-CODE
100
+ [
101
+ (x) => { return (x instanceof Object || x instanceof Array) && !(x instanceof Date) && !(x instanceof Function) },
102
+ (x) => { return x instanceof Date },
103
+ (x) => { return x.getTime(x) },
104
+ (x) => { return typeof x === 'symbol' },
105
+ (x) => { var r = x.description; return r === undefined ? 'undefined' : r },
106
+ (x) => { return new Date(x) },
107
+ (x) => { return new Array(x) },
108
+ ]
109
+ CODE
110
+ end
111
+
112
+ def dispose_unsafe
113
+ @context.close
114
+ end
115
+
116
+ def eval_unsafe(str, filename)
117
+ @entered = true
118
+ if !@has_entered && @snapshot
119
+ snapshot_src = encode(@snapshot.instance_variable_get(:@source))
120
+ begin
121
+ eval_in_context(snapshot_src, filename)
122
+ rescue Polyglot::ForeignException => e
123
+ raise RuntimeError, e.message, e.backtrace
124
+ end
125
+ end
126
+ @has_entered = true
127
+ raise RuntimeError, "TruffleRuby does not support eval after stop" if @stopped
128
+ raise TypeError, "wrong type argument #{str.class} (should be a string)" unless str.is_a?(String)
129
+ raise TypeError, "wrong type argument #{filename.class} (should be a string)" unless filename.nil? || filename.is_a?(String)
130
+
131
+ str = encode(str)
132
+ begin
133
+ translate do
134
+ eval_in_context(str, filename)
135
+ end
136
+ rescue Polyglot::ForeignException => e
137
+ raise RuntimeError, e.message, e.backtrace
138
+ rescue ::RuntimeError => e
139
+ if @current_exception
140
+ e = @current_exception
141
+ @current_exception = nil
142
+ raise e
143
+ else
144
+ raise e, e.message
145
+ end
146
+ end
147
+ ensure
148
+ @entered = false
149
+ end
150
+
151
+ def call_unsafe(function_name, *arguments)
152
+ @entered = true
153
+ if !@has_entered && @snapshot
154
+ src = encode(@snapshot.instance_variable_get(:source))
155
+ begin
156
+ eval_in_context(src)
157
+ rescue Polyglot::ForeignException => e
158
+ raise RuntimeError, e.message, e.backtrace
159
+ end
160
+ end
161
+ @has_entered = true
162
+ raise RuntimeError, "TruffleRuby does not support call after stop" if @stopped
163
+ begin
164
+ translate do
165
+ function = eval_in_context(function_name)
166
+ function.call(*convert_ruby_to_js(arguments))
167
+ end
168
+ rescue Polyglot::ForeignException => e
169
+ raise RuntimeError, e.message, e.backtrace
170
+ end
171
+ ensure
172
+ @entered = false
173
+ end
174
+
175
+ def create_isolate_value
176
+ # Returning a dummy object since TruffleRuby does not have a 1-1 concept with isolate.
177
+ # However, code and ASTs are shared between contexts.
178
+ Isolate.new
179
+ end
180
+
181
+ def isolate_mutex
182
+ @isolate_mutex
183
+ end
184
+
185
+ def translate
186
+ convert_js_to_ruby yield
187
+ rescue Object => e
188
+ message = e.message
189
+ if @current_exception
190
+ raise @current_exception
191
+ elsif e.message && e.message.start_with?('SyntaxError:')
192
+ error_class = MiniRacer::ParseError
193
+ elsif e.is_a?(MiniRacer::ScriptTerminatedError)
194
+ error_class = MiniRacer::ScriptTerminatedError
195
+ else
196
+ error_class = MiniRacer::RuntimeError
197
+ end
198
+
199
+ if error_class == MiniRacer::RuntimeError
200
+ bls = e.backtrace_locations&.select { |bl| bl&.source_location&.language == 'js' }
201
+ if bls && !bls.empty?
202
+ if '(eval)' != bls[0].path
203
+ message = "#{e.message}\n at #{bls[0]}\n" + bls[1..].map(&:to_s).join("\n")
204
+ else
205
+ message = "#{e.message}\n" + bls.map(&:to_s).join("\n")
206
+ end
207
+ end
208
+ raise error_class, message
209
+ else
210
+ raise error_class, message, e.backtrace
211
+ end
212
+ end
213
+
214
+ def convert_js_to_ruby(value)
215
+ case value
216
+ when true, false, Integer, Float
217
+ value
218
+ else
219
+ if value.nil?
220
+ nil
221
+ elsif value.respond_to?(:call)
222
+ MiniRacer::JavaScriptFunction.new
223
+ elsif value.respond_to?(:to_str)
224
+ value.to_str.dup
225
+ elsif value.respond_to?(:to_ary)
226
+ value.to_ary.map do |e|
227
+ if e.respond_to?(:call)
228
+ nil
229
+ else
230
+ convert_js_to_ruby(e)
231
+ end
232
+ end
233
+ elsif time?(value)
234
+ js_date_to_time(value)
235
+ elsif symbol?(value)
236
+ js_symbol_to_symbol(value)
237
+ else
238
+ object = value
239
+ h = {}
240
+ object.instance_variables.each do |member|
241
+ v = object[member]
242
+ unless v.respond_to?(:call)
243
+ h[member.to_s] = convert_js_to_ruby(v)
244
+ end
245
+ end
246
+ h
247
+ end
248
+ end
249
+ end
250
+
251
+ def object_or_array?(val)
252
+ @is_object_or_array_func.call(val)
253
+ end
254
+
255
+ def time?(value)
256
+ @is_time_func.call(value)
257
+ end
258
+
259
+ def js_date_to_time(value)
260
+ millis = @js_date_to_time_func.call(value)
261
+ Time.at(Rational(millis, 1000))
262
+ end
263
+
264
+ def symbol?(value)
265
+ @is_symbol_func.call(value)
266
+ end
267
+
268
+ def js_symbol_to_symbol(value)
269
+ @js_symbol_to_symbol_func.call(value).to_s.to_sym
270
+ end
271
+
272
+ def js_new_date(value)
273
+ @js_new_date_func.call(value)
274
+ end
275
+
276
+ def js_new_array(size)
277
+ @js_new_array_func.call(size)
278
+ end
279
+
280
+ def convert_ruby_to_js(value)
281
+ case value
282
+ when nil, true, false, Integer, Float
283
+ value
284
+ when Array
285
+ ary = js_new_array(value.size)
286
+ value.each_with_index do |v, i|
287
+ ary[i] = convert_ruby_to_js(v)
288
+ end
289
+ ary
290
+ when Hash
291
+ h = @js_object.new
292
+ value.each_pair do |k, v|
293
+ h[convert_ruby_to_js(k.to_s)] = convert_ruby_to_js(v)
294
+ end
295
+ h
296
+ when String, Symbol
297
+ Truffle::Interop.as_truffle_string value
298
+ when Time
299
+ js_new_date(value.to_f * 1000)
300
+ when DateTime
301
+ js_new_date(value.to_time.to_f * 1000)
302
+ else
303
+ "Undefined Conversion"
304
+ end
305
+ end
306
+
307
+ def encode(string)
308
+ raise ArgumentError unless string
309
+ string.encode(::Encoding::UTF_8)
310
+ end
311
+
312
+ class_eval <<-'RUBY', "(mini_racer)", 1
313
+ def eval_in_context(code, file = nil); code = ('"use strict";' + code) if Context.instance_variable_get(:@use_strict); @context.eval('js', code, file || '(mini_racer)'); end
314
+ RUBY
315
+
316
+ end
317
+
318
+ class Isolate
319
+ def init_with_snapshot(snapshot)
320
+ # TruffleRuby does not have a 1-1 concept with isolate.
321
+ # However, isolate can hold a snapshot, and code and ASTs are shared between contexts.
322
+ @snapshot = snapshot
323
+ end
324
+
325
+ def low_memory_notification
326
+ GC.start
327
+ end
328
+
329
+ def idle_notification(idle_time)
330
+ true
331
+ end
332
+ end
333
+
334
+ class Platform
335
+ def self.set_flag_as_str!(flag)
336
+ raise TypeError, "wrong type argument #{flag.class} (should be a string)" unless flag.is_a?(String)
337
+ raise MiniRacer::PlatformAlreadyInitialized, "The platform is already initialized." if Context.instance_variable_get(:@context_initialized)
338
+ Context.instance_variable_set(:@use_strict, true) if "--use_strict" == flag
339
+ end
340
+ end
341
+
342
+ class Snapshot
343
+ def load(str)
344
+ raise TypeError, "wrong type argument #{str.class} (should be a string)" unless str.is_a?(String)
345
+ # Intentionally noop since TruffleRuby mocks the snapshot API
346
+ end
347
+
348
+ def warmup_unsafe!(src)
349
+ raise TypeError, "wrong type argument #{src.class} (should be a string)" unless src.is_a?(String)
350
+ # Intentionally noop since TruffleRuby mocks the snapshot API
351
+ # by replaying snapshot source before the first eval/call
352
+ self
353
+ end
354
+ end
355
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacer
4
- VERSION = "0.3.1"
4
+ VERSION = "0.6.4"
5
+ LIBV8_NODE_VERSION = "~> 16.19.0.0"
5
6
  end
data/lib/mini_racer.rb CHANGED
@@ -1,10 +1,28 @@
1
1
  require "mini_racer/version"
2
- require "mini_racer_extension"
2
+ require "pathname"
3
+
4
+ if RUBY_ENGINE == "truffleruby"
5
+ require "mini_racer/truffleruby"
6
+ else
7
+ require "mini_racer_loader"
8
+ ext_filename = "mini_racer_extension.#{RbConfig::CONFIG['DLEXT']}"
9
+ ext_path = Gem.loaded_specs['mini_racer'].require_paths
10
+ .map { |p| (p = Pathname.new(p)).absolute? ? p : Pathname.new(__dir__).parent + p }
11
+ ext_found = ext_path.map { |p| p + ext_filename }.find { |p| p.file? }
12
+
13
+ raise LoadError, "Could not find #{ext_filename} in #{ext_path.map(&:to_s)}" unless ext_found
14
+ MiniRacer::Loader.load(ext_found.to_s)
15
+ end
16
+
3
17
  require "thread"
4
18
  require "json"
19
+ require "io/wait"
5
20
 
6
21
  module MiniRacer
7
22
 
23
+ MARSHAL_STACKDEPTH_DEFAULT = 2**9-2
24
+ MARSHAL_STACKDEPTH_MAX_VALUE = 2**10-2
25
+
8
26
  class Error < ::StandardError; end
9
27
 
10
28
  class ContextDisposedError < Error; end
@@ -130,10 +148,10 @@ module MiniRacer
130
148
  end
131
149
  end
132
150
 
133
- def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil)
151
+ def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil, marshal_stack_depth: nil)
134
152
  options ||= {}
135
153
 
136
- check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
154
+ check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, marshal_stack_depth: marshal_stack_depth, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
137
155
 
138
156
  @functions = {}
139
157
  @timeout = nil
@@ -141,6 +159,7 @@ module MiniRacer
141
159
  @current_exception = nil
142
160
  @timeout = timeout
143
161
  @max_memory = max_memory
162
+ @marshal_stack_depth = marshal_stack_depth
144
163
 
145
164
  # false signals it should be fetched if requested
146
165
  @isolate = isolate || false
@@ -188,7 +207,7 @@ module MiniRacer
188
207
  end
189
208
 
190
209
  if !(File === f)
191
- raise ArgumentError("file_or_io")
210
+ raise ArgumentError, "file_or_io"
192
211
  end
193
212
 
194
213
  write_heap_snapshot_unsafe(f)
@@ -335,7 +354,7 @@ module MiniRacer
335
354
 
336
355
  t = Thread.new do
337
356
  begin
338
- result = IO.select([rp],[],[],(@timeout/1000.0))
357
+ result = rp.wait_readable(@timeout/1000.0)
339
358
  if !result
340
359
  mutex.synchronize do
341
360
  stop unless done
@@ -352,7 +371,7 @@ module MiniRacer
352
371
  done = true
353
372
  end
354
373
 
355
- wp.write("done")
374
+ wp.close
356
375
 
357
376
  # ensure we do not leak a thread in state
358
377
  t.join
@@ -361,19 +380,17 @@ module MiniRacer
361
380
  rval
362
381
  ensure
363
382
  # exceptions need to be handled
364
- if t && wp
365
- wp.write("done")
366
- t.join
367
- end
368
- wp.close if wp
369
- rp.close if rp
383
+ wp&.close
384
+ t&.join
385
+ rp&.close
370
386
  end
371
387
 
372
- def check_init_options!(isolate:, snapshot:, max_memory:, ensure_gc_after_idle:, timeout:)
388
+ def check_init_options!(isolate:, snapshot:, max_memory:, marshal_stack_depth:, ensure_gc_after_idle:, timeout:)
373
389
  assert_option_is_nil_or_a('isolate', isolate, Isolate)
374
390
  assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
375
391
 
376
- assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000)
392
+ assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000, max_value: 2**32-1)
393
+ assert_numeric_or_nil('marshal_stack_depth', marshal_stack_depth, min_value: 1, max_value: MARSHAL_STACKDEPTH_MAX_VALUE)
377
394
  assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
378
395
  assert_numeric_or_nil('timeout', timeout, min_value: 1)
379
396
 
@@ -382,9 +399,13 @@ module MiniRacer
382
399
  end
383
400
  end
384
401
 
385
- def assert_numeric_or_nil(option_name, object, min_value:)
402
+ def assert_numeric_or_nil(option_name, object, min_value:, max_value: nil)
403
+ if max_value && object.is_a?(Numeric) && object > max_value
404
+ raise ArgumentError, "#{option_name} must be less than or equal to #{max_value}"
405
+ end
406
+
386
407
  if object.is_a?(Numeric) && object < min_value
387
- raise ArgumentError, "#{option_name} must be larger than #{min_value}"
408
+ raise ArgumentError, "#{option_name} must be larger than or equal to #{min_value}"
388
409
  end
389
410
 
390
411
  if !object.nil? && !object.is_a?(Numeric)
data/mini_racer.gemspec CHANGED
@@ -22,8 +22,6 @@ Gem::Specification.new do |spec|
22
22
  }
23
23
 
24
24
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(benchmark|test|spec|features|examples)/}) }
25
- spec.bindir = "exe"
26
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
25
  spec.require_paths = ["lib"]
28
26
 
29
27
  spec.add_development_dependency "bundler"
@@ -32,10 +30,10 @@ Gem::Specification.new do |spec|
32
30
  spec.add_development_dependency "rake-compiler"
33
31
  spec.add_development_dependency "m"
34
32
 
35
- spec.add_dependency 'libv8', '~> 8.4.255'
33
+ spec.add_dependency 'libv8-node', MiniRacer::LIBV8_NODE_VERSION
36
34
  spec.require_paths = ["lib", "ext"]
37
35
 
38
- spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
36
+ spec.extensions = ["ext/mini_racer_loader/extconf.rb", "ext/mini_racer_extension/extconf.rb"]
39
37
 
40
- spec.required_ruby_version = '>= 2.3'
38
+ spec.required_ruby_version = '>= 2.6'
41
39
  end