mini_racer 0.2.4 → 0.4.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 +4 -4
- data/.dockerignore +12 -0
- data/.github/workflows/ci.yml +80 -0
- data/.gitignore +1 -0
- data/.travis.yml +17 -9
- data/CHANGELOG +98 -0
- data/Dockerfile +21 -0
- data/LICENSE.txt +1 -1
- data/README.md +44 -4
- data/Rakefile +42 -0
- data/ext/mini_racer_extension/extconf.rb +16 -3
- data/ext/mini_racer_extension/mini_racer_extension.cc +611 -318
- data/ext/mini_racer_loader/extconf.rb +8 -0
- data/ext/mini_racer_loader/mini_racer_loader.c +123 -0
- data/lib/mini_racer/version.rb +4 -1
- data/lib/mini_racer.rb +113 -16
- data/mini_racer.gemspec +13 -6
- metadata +43 -20
@@ -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
|
+
}
|
data/lib/mini_racer/version.rb
CHANGED
data/lib/mini_racer.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
require "mini_racer/version"
|
2
|
-
require "
|
2
|
+
require "mini_racer_loader"
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
ext_filename = "mini_racer_extension.#{RbConfig::CONFIG['DLEXT']}"
|
6
|
+
ext_path = Gem.loaded_specs['mini_racer'].require_paths
|
7
|
+
.map { |p| (p = Pathname.new(p)).absolute? ? p : Pathname.new(__dir__).parent + p }
|
8
|
+
ext_found = ext_path.map { |p| p + ext_filename }.find { |p| p.file? }
|
9
|
+
|
10
|
+
raise LoadError, "Could not find #{ext_filename} in #{ext_path.map(&:to_s)}" unless ext_found
|
11
|
+
MiniRacer::Loader.load(ext_found.to_s)
|
12
|
+
|
3
13
|
require "thread"
|
4
14
|
require "json"
|
5
15
|
|
@@ -130,21 +140,29 @@ module MiniRacer
|
|
130
140
|
end
|
131
141
|
end
|
132
142
|
|
133
|
-
def initialize(
|
143
|
+
def initialize(max_memory: nil, timeout: nil, isolate: nil, ensure_gc_after_idle: nil, snapshot: nil)
|
134
144
|
options ||= {}
|
135
145
|
|
136
|
-
check_init_options!(
|
146
|
+
check_init_options!(isolate: isolate, snapshot: snapshot, max_memory: max_memory, ensure_gc_after_idle: ensure_gc_after_idle, timeout: timeout)
|
137
147
|
|
138
148
|
@functions = {}
|
139
149
|
@timeout = nil
|
140
150
|
@max_memory = nil
|
141
151
|
@current_exception = nil
|
142
|
-
@timeout =
|
143
|
-
|
144
|
-
|
145
|
-
end
|
152
|
+
@timeout = timeout
|
153
|
+
@max_memory = max_memory
|
154
|
+
|
146
155
|
# false signals it should be fetched if requested
|
147
|
-
@isolate =
|
156
|
+
@isolate = isolate || false
|
157
|
+
|
158
|
+
@ensure_gc_after_idle = ensure_gc_after_idle
|
159
|
+
|
160
|
+
if @ensure_gc_after_idle
|
161
|
+
@last_eval = nil
|
162
|
+
@ensure_gc_thread = nil
|
163
|
+
@ensure_gc_mutex = Mutex.new
|
164
|
+
end
|
165
|
+
|
148
166
|
@disposed = false
|
149
167
|
|
150
168
|
@callback_mutex = Mutex.new
|
@@ -153,7 +171,7 @@ module MiniRacer
|
|
153
171
|
@eval_thread = nil
|
154
172
|
|
155
173
|
# defined in the C class
|
156
|
-
init_unsafe(
|
174
|
+
init_unsafe(isolate, snapshot)
|
157
175
|
end
|
158
176
|
|
159
177
|
def isolate
|
@@ -167,6 +185,28 @@ module MiniRacer
|
|
167
185
|
eval(File.read(filename))
|
168
186
|
end
|
169
187
|
|
188
|
+
def write_heap_snapshot(file_or_io)
|
189
|
+
f = nil
|
190
|
+
implicit = false
|
191
|
+
|
192
|
+
|
193
|
+
if String === file_or_io
|
194
|
+
f = File.open(file_or_io, "w")
|
195
|
+
implicit = true
|
196
|
+
else
|
197
|
+
f = file_or_io
|
198
|
+
end
|
199
|
+
|
200
|
+
if !(File === f)
|
201
|
+
raise ArgumentError("file_or_io")
|
202
|
+
end
|
203
|
+
|
204
|
+
write_heap_snapshot_unsafe(f)
|
205
|
+
|
206
|
+
ensure
|
207
|
+
f.close if implicit
|
208
|
+
end
|
209
|
+
|
170
210
|
def eval(str, options=nil)
|
171
211
|
raise(ContextDisposedError, 'attempted to call eval on a disposed context!') if @disposed
|
172
212
|
|
@@ -181,6 +221,7 @@ module MiniRacer
|
|
181
221
|
end
|
182
222
|
ensure
|
183
223
|
@eval_thread = nil
|
224
|
+
ensure_gc_thread if @ensure_gc_after_idle
|
184
225
|
end
|
185
226
|
|
186
227
|
def call(function_name, *arguments)
|
@@ -194,15 +235,17 @@ module MiniRacer
|
|
194
235
|
end
|
195
236
|
ensure
|
196
237
|
@eval_thread = nil
|
238
|
+
ensure_gc_thread if @ensure_gc_after_idle
|
197
239
|
end
|
198
240
|
|
199
241
|
def dispose
|
200
242
|
return if @disposed
|
201
243
|
isolate_mutex.synchronize do
|
244
|
+
return if @disposed
|
202
245
|
dispose_unsafe
|
246
|
+
@disposed = true
|
247
|
+
@isolate = nil # allow it to be garbage collected, if set
|
203
248
|
end
|
204
|
-
@disposed = true
|
205
|
-
@isolate = nil # allow it to be garbage collected, if set
|
206
249
|
end
|
207
250
|
|
208
251
|
|
@@ -251,6 +294,38 @@ module MiniRacer
|
|
251
294
|
|
252
295
|
private
|
253
296
|
|
297
|
+
def ensure_gc_thread
|
298
|
+
@last_eval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
299
|
+
@ensure_gc_mutex.synchronize do
|
300
|
+
@ensure_gc_thread = nil if !@ensure_gc_thread&.alive?
|
301
|
+
@ensure_gc_thread ||= Thread.new do
|
302
|
+
ensure_gc_after_idle_seconds = @ensure_gc_after_idle / 1000.0
|
303
|
+
done = false
|
304
|
+
while !done
|
305
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
306
|
+
|
307
|
+
if @disposed
|
308
|
+
@ensure_gc_thread = nil
|
309
|
+
break
|
310
|
+
end
|
311
|
+
|
312
|
+
if !@eval_thread && ensure_gc_after_idle_seconds < now - @last_eval
|
313
|
+
@ensure_gc_mutex.synchronize do
|
314
|
+
isolate_mutex.synchronize do
|
315
|
+
if !@eval_thread
|
316
|
+
isolate.low_memory_notification if !@disposed
|
317
|
+
@ensure_gc_thread = nil
|
318
|
+
done = true
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
sleep ensure_gc_after_idle_seconds if !done
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
254
329
|
def stop_attached
|
255
330
|
@callback_mutex.synchronize{
|
256
331
|
if @callback_running
|
@@ -291,20 +366,42 @@ module MiniRacer
|
|
291
366
|
|
292
367
|
# ensure we do not leak a thread in state
|
293
368
|
t.join
|
369
|
+
t = nil
|
294
370
|
|
295
371
|
rval
|
296
|
-
|
372
|
+
ensure
|
373
|
+
# exceptions need to be handled
|
374
|
+
if t && wp
|
375
|
+
wp.write("done")
|
376
|
+
t.join
|
377
|
+
end
|
378
|
+
wp.close if wp
|
379
|
+
rp.close if rp
|
297
380
|
end
|
298
381
|
|
299
|
-
def check_init_options!(
|
300
|
-
assert_option_is_nil_or_a('isolate',
|
301
|
-
assert_option_is_nil_or_a('snapshot',
|
382
|
+
def check_init_options!(isolate:, snapshot:, max_memory:, ensure_gc_after_idle:, timeout:)
|
383
|
+
assert_option_is_nil_or_a('isolate', isolate, Isolate)
|
384
|
+
assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
|
302
385
|
|
303
|
-
|
386
|
+
assert_numeric_or_nil('max_memory', max_memory, min_value: 10_000)
|
387
|
+
assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
|
388
|
+
assert_numeric_or_nil('timeout', timeout, min_value: 1)
|
389
|
+
|
390
|
+
if isolate && snapshot
|
304
391
|
raise ArgumentError, 'can only pass one of isolate and snapshot options'
|
305
392
|
end
|
306
393
|
end
|
307
394
|
|
395
|
+
def assert_numeric_or_nil(option_name, object, min_value:)
|
396
|
+
if object.is_a?(Numeric) && object < min_value
|
397
|
+
raise ArgumentError, "#{option_name} must be larger than #{min_value}"
|
398
|
+
end
|
399
|
+
|
400
|
+
if !object.nil? && !object.is_a?(Numeric)
|
401
|
+
raise ArgumentError, "#{option_name} must be a number, passed a #{object.inspect}"
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
308
405
|
def assert_option_is_nil_or_a(option_name, object, klass)
|
309
406
|
unless object.nil? || object.is_a?(klass)
|
310
407
|
raise ArgumentError, "#{option_name} must be a #{klass} object, passed a #{object.inspect}"
|
data/mini_racer.gemspec
CHANGED
@@ -14,21 +14,28 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = "https://github.com/discourse/mini_racer"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
spec.metadata = {
|
18
|
+
"bug_tracker_uri" => "https://github.com/discourse/mini_racer/issues",
|
19
|
+
"changelog_uri" => "https://github.com/discourse/mini_racer/blob/v#{spec.version}/CHANGELOG",
|
20
|
+
"documentation_uri" => "https://www.rubydoc.info/gems/mini_racer/#{spec.version}",
|
21
|
+
"source_code_uri" => "https://github.com/discourse/mini_racer/tree/v#{spec.version}",
|
22
|
+
}
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(benchmark|test|spec|features|examples)/}) }
|
19
25
|
spec.bindir = "exe"
|
20
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
27
|
spec.require_paths = ["lib"]
|
22
28
|
|
23
|
-
spec.add_development_dependency "bundler"
|
24
|
-
spec.add_development_dependency "rake", "
|
29
|
+
spec.add_development_dependency "bundler"
|
30
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
25
31
|
spec.add_development_dependency "minitest", "~> 5.0"
|
26
32
|
spec.add_development_dependency "rake-compiler"
|
33
|
+
spec.add_development_dependency "m"
|
27
34
|
|
28
|
-
spec.add_dependency 'libv8',
|
35
|
+
spec.add_dependency 'libv8-node', MiniRacer::LIBV8_NODE_VERSION
|
29
36
|
spec.require_paths = ["lib", "ext"]
|
30
37
|
|
31
|
-
spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
|
38
|
+
spec.extensions = ["ext/mini_racer_loader/extconf.rb", "ext/mini_racer_extension/extconf.rb"]
|
32
39
|
|
33
40
|
spec.required_ruby_version = '>= 2.3'
|
34
41
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_racer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 12.3.3
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 12.3.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,31 +67,49 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: m
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
76
|
-
type: :
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: libv8-node
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 15.14.0.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 15.14.0.0
|
83
97
|
description: Minimal embedded v8 engine for Ruby
|
84
98
|
email:
|
85
99
|
- sam.saffron@gmail.com
|
86
100
|
executables: []
|
87
101
|
extensions:
|
102
|
+
- ext/mini_racer_loader/extconf.rb
|
88
103
|
- ext/mini_racer_extension/extconf.rb
|
89
104
|
extra_rdoc_files: []
|
90
105
|
files:
|
106
|
+
- ".dockerignore"
|
107
|
+
- ".github/workflows/ci.yml"
|
91
108
|
- ".gitignore"
|
92
109
|
- ".travis.yml"
|
93
110
|
- CHANGELOG
|
94
111
|
- CODE_OF_CONDUCT.md
|
112
|
+
- Dockerfile
|
95
113
|
- Gemfile
|
96
114
|
- LICENSE.txt
|
97
115
|
- README.md
|
@@ -100,14 +118,20 @@ files:
|
|
100
118
|
- bin/setup
|
101
119
|
- ext/mini_racer_extension/extconf.rb
|
102
120
|
- ext/mini_racer_extension/mini_racer_extension.cc
|
121
|
+
- ext/mini_racer_loader/extconf.rb
|
122
|
+
- ext/mini_racer_loader/mini_racer_loader.c
|
103
123
|
- lib/mini_racer.rb
|
104
124
|
- lib/mini_racer/version.rb
|
105
125
|
- mini_racer.gemspec
|
106
126
|
homepage: https://github.com/discourse/mini_racer
|
107
127
|
licenses:
|
108
128
|
- MIT
|
109
|
-
metadata:
|
110
|
-
|
129
|
+
metadata:
|
130
|
+
bug_tracker_uri: https://github.com/discourse/mini_racer/issues
|
131
|
+
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.4.0/CHANGELOG
|
132
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.4.0
|
133
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.4.0
|
134
|
+
post_install_message:
|
111
135
|
rdoc_options: []
|
112
136
|
require_paths:
|
113
137
|
- lib
|
@@ -123,9 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
147
|
- !ruby/object:Gem::Version
|
124
148
|
version: '0'
|
125
149
|
requirements: []
|
126
|
-
|
127
|
-
|
128
|
-
signing_key:
|
150
|
+
rubygems_version: 3.2.2
|
151
|
+
signing_key:
|
129
152
|
specification_version: 4
|
130
153
|
summary: Minimal embedded v8 for Ruby
|
131
154
|
test_files: []
|