mini_racer 0.4.0 → 0.6.3

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.
@@ -1,8 +1,13 @@
1
1
  require 'mkmf'
2
2
 
3
+ if RUBY_ENGINE == "truffleruby"
4
+ File.write("Makefile", dummy_makefile($srcdir).join(""))
5
+ return
6
+ end
7
+
3
8
  extension_name = 'mini_racer_loader'
4
9
  dir_config extension_name
5
10
 
6
- $CPPFLAGS += " -fvisibility=hidden "
11
+ $CXXFLAGS += " -fvisibility=hidden "
7
12
 
8
13
  create_makefile extension_name
@@ -0,0 +1,353 @@
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
+ warn "You also need to install the 'js' component with 'gu install js' on GraalVM 22.2+", uplevel: 0 if $VERBOSE
75
+ end
76
+
77
+ @context = Polyglot::InnerContext.new(on_cancelled: -> {
78
+ raise ScriptTerminatedError, 'JavaScript was terminated (either by timeout or explicitly)'
79
+ })
80
+ Context.instance_variable_set(:@context_initialized, true)
81
+ @js_object = @context.eval('js', 'Object')
82
+ @isolate_mutex = Mutex.new
83
+ @stopped = false
84
+ @entered = false
85
+ @has_entered = false
86
+ @current_exception = nil
87
+ if isolate && snapshot
88
+ isolate.instance_variable_set(:@snapshot, snapshot)
89
+ end
90
+ if snapshot
91
+ @snapshot = snapshot
92
+ elsif isolate
93
+ @snapshot = isolate.instance_variable_get(:@snapshot)
94
+ else
95
+ @snapshot = nil
96
+ end
97
+ @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
98
+ [
99
+ (x) => { return (x instanceof Object || x instanceof Array) && !(x instanceof Date) && !(x instanceof Function) },
100
+ (x) => { return x instanceof Date },
101
+ (x) => { return x.getTime(x) },
102
+ (x) => { return typeof x === 'symbol' },
103
+ (x) => { var r = x.description; return r === undefined ? 'undefined' : r },
104
+ (x) => { return new Date(x) },
105
+ (x) => { return new Array(x) },
106
+ ]
107
+ CODE
108
+ end
109
+
110
+ def dispose_unsafe
111
+ @context.close
112
+ end
113
+
114
+ def eval_unsafe(str, filename)
115
+ @entered = true
116
+ if !@has_entered && @snapshot
117
+ snapshot_src = encode(@snapshot.instance_variable_get(:@source))
118
+ begin
119
+ eval_in_context(snapshot_src, filename)
120
+ rescue Polyglot::ForeignException => e
121
+ raise RuntimeError, e.message, e.backtrace
122
+ end
123
+ end
124
+ @has_entered = true
125
+ raise RuntimeError, "TruffleRuby does not support eval after stop" if @stopped
126
+ raise TypeError, "wrong type argument #{str.class} (should be a string)" unless str.is_a?(String)
127
+ raise TypeError, "wrong type argument #{filename.class} (should be a string)" unless filename.nil? || filename.is_a?(String)
128
+
129
+ str = encode(str)
130
+ begin
131
+ translate do
132
+ eval_in_context(str, filename)
133
+ end
134
+ rescue Polyglot::ForeignException => e
135
+ raise RuntimeError, e.message, e.backtrace
136
+ rescue ::RuntimeError => e
137
+ if @current_exception
138
+ e = @current_exception
139
+ @current_exception = nil
140
+ raise e
141
+ else
142
+ raise e, e.message
143
+ end
144
+ end
145
+ ensure
146
+ @entered = false
147
+ end
148
+
149
+ def call_unsafe(function_name, *arguments)
150
+ @entered = true
151
+ if !@has_entered && @snapshot
152
+ src = encode(@snapshot.instance_variable_get(:source))
153
+ begin
154
+ eval_in_context(src)
155
+ rescue Polyglot::ForeignException => e
156
+ raise RuntimeError, e.message, e.backtrace
157
+ end
158
+ end
159
+ @has_entered = true
160
+ raise RuntimeError, "TruffleRuby does not support call after stop" if @stopped
161
+ begin
162
+ translate do
163
+ function = eval_in_context(function_name)
164
+ function.call(*convert_ruby_to_js(arguments))
165
+ end
166
+ rescue Polyglot::ForeignException => e
167
+ raise RuntimeError, e.message, e.backtrace
168
+ end
169
+ ensure
170
+ @entered = false
171
+ end
172
+
173
+ def create_isolate_value
174
+ # Returning a dummy object since TruffleRuby does not have a 1-1 concept with isolate.
175
+ # However, code and ASTs are shared between contexts.
176
+ Isolate.new
177
+ end
178
+
179
+ def isolate_mutex
180
+ @isolate_mutex
181
+ end
182
+
183
+ def translate
184
+ convert_js_to_ruby yield
185
+ rescue Object => e
186
+ message = e.message
187
+ if @current_exception
188
+ raise @current_exception
189
+ elsif e.message && e.message.start_with?('SyntaxError:')
190
+ error_class = MiniRacer::ParseError
191
+ elsif e.is_a?(MiniRacer::ScriptTerminatedError)
192
+ error_class = MiniRacer::ScriptTerminatedError
193
+ else
194
+ error_class = MiniRacer::RuntimeError
195
+ end
196
+
197
+ if error_class == MiniRacer::RuntimeError
198
+ bls = e.backtrace_locations&.select { |bl| bl&.source_location&.language == 'js' }
199
+ if bls && !bls.empty?
200
+ if '(eval)' != bls[0].path
201
+ message = "#{e.message}\n at #{bls[0]}\n" + bls[1..].map(&:to_s).join("\n")
202
+ else
203
+ message = "#{e.message}\n" + bls.map(&:to_s).join("\n")
204
+ end
205
+ end
206
+ raise error_class, message
207
+ else
208
+ raise error_class, message, e.backtrace
209
+ end
210
+ end
211
+
212
+ def convert_js_to_ruby(value)
213
+ case value
214
+ when true, false, Integer, Float
215
+ value
216
+ else
217
+ if value.nil?
218
+ nil
219
+ elsif value.respond_to?(:call)
220
+ MiniRacer::JavaScriptFunction.new
221
+ elsif value.respond_to?(:to_str)
222
+ value.to_str.dup
223
+ elsif value.respond_to?(:to_ary)
224
+ value.to_ary.map do |e|
225
+ if e.respond_to?(:call)
226
+ nil
227
+ else
228
+ convert_js_to_ruby(e)
229
+ end
230
+ end
231
+ elsif time?(value)
232
+ js_date_to_time(value)
233
+ elsif symbol?(value)
234
+ js_symbol_to_symbol(value)
235
+ else
236
+ object = value
237
+ h = {}
238
+ object.instance_variables.each do |member|
239
+ v = object[member]
240
+ unless v.respond_to?(:call)
241
+ h[member.to_s] = convert_js_to_ruby(v)
242
+ end
243
+ end
244
+ h
245
+ end
246
+ end
247
+ end
248
+
249
+ def object_or_array?(val)
250
+ @is_object_or_array_func.call(val)
251
+ end
252
+
253
+ def time?(value)
254
+ @is_time_func.call(value)
255
+ end
256
+
257
+ def js_date_to_time(value)
258
+ millis = @js_date_to_time_func.call(value)
259
+ Time.at(Rational(millis, 1000))
260
+ end
261
+
262
+ def symbol?(value)
263
+ @is_symbol_func.call(value)
264
+ end
265
+
266
+ def js_symbol_to_symbol(value)
267
+ @js_symbol_to_symbol_func.call(value).to_s.to_sym
268
+ end
269
+
270
+ def js_new_date(value)
271
+ @js_new_date_func.call(value)
272
+ end
273
+
274
+ def js_new_array(size)
275
+ @js_new_array_func.call(size)
276
+ end
277
+
278
+ def convert_ruby_to_js(value)
279
+ case value
280
+ when nil, true, false, Integer, Float
281
+ value
282
+ when Array
283
+ ary = js_new_array(value.size)
284
+ value.each_with_index do |v, i|
285
+ ary[i] = convert_ruby_to_js(v)
286
+ end
287
+ ary
288
+ when Hash
289
+ h = @js_object.new
290
+ value.each_pair do |k, v|
291
+ h[convert_ruby_to_js(k.to_s)] = convert_ruby_to_js(v)
292
+ end
293
+ h
294
+ when String, Symbol
295
+ Truffle::Interop.as_truffle_string value
296
+ when Time
297
+ js_new_date(value.to_f * 1000)
298
+ when DateTime
299
+ js_new_date(value.to_time.to_f * 1000)
300
+ else
301
+ "Undefined Conversion"
302
+ end
303
+ end
304
+
305
+ def encode(string)
306
+ raise ArgumentError unless string
307
+ string.encode(::Encoding::UTF_8)
308
+ end
309
+
310
+ class_eval <<-'RUBY', "(mini_racer)", 1
311
+ 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
312
+ RUBY
313
+
314
+ end
315
+
316
+ class Isolate
317
+ def init_with_snapshot(snapshot)
318
+ # TruffleRuby does not have a 1-1 concept with isolate.
319
+ # However, isolate can hold a snapshot, and code and ASTs are shared between contexts.
320
+ @snapshot = snapshot
321
+ end
322
+
323
+ def low_memory_notification
324
+ GC.start
325
+ end
326
+
327
+ def idle_notification(idle_time)
328
+ true
329
+ end
330
+ end
331
+
332
+ class Platform
333
+ def self.set_flag_as_str!(flag)
334
+ raise TypeError, "wrong type argument #{flag.class} (should be a string)" unless flag.is_a?(String)
335
+ raise MiniRacer::PlatformAlreadyInitialized, "The platform is already initialized." if Context.instance_variable_get(:@context_initialized)
336
+ Context.instance_variable_set(:@use_strict, true) if "--use_strict" == flag
337
+ end
338
+ end
339
+
340
+ class Snapshot
341
+ def load(str)
342
+ raise TypeError, "wrong type argument #{str.class} (should be a string)" unless str.is_a?(String)
343
+ # Intentionally noop since TruffleRuby mocks the snapshot API
344
+ end
345
+
346
+ def warmup_unsafe!(src)
347
+ raise TypeError, "wrong type argument #{src.class} (should be a string)" unless src.is_a?(String)
348
+ # Intentionally noop since TruffleRuby mocks the snapshot API
349
+ # by replaying snapshot source before the first eval/call
350
+ self
351
+ end
352
+ end
353
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacer
4
- VERSION = "0.4.0"
5
- LIBV8_NODE_VERSION = "~> 15.14.0.0"
4
+ VERSION = "0.6.3"
5
+ LIBV8_NODE_VERSION = "~> 16.10.0.0"
6
6
  end
data/lib/mini_racer.rb CHANGED
@@ -1,20 +1,28 @@
1
1
  require "mini_racer/version"
2
- require "mini_racer_loader"
3
2
  require "pathname"
4
3
 
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)
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
12
16
 
13
17
  require "thread"
14
18
  require "json"
19
+ require "io/wait"
15
20
 
16
21
  module MiniRacer
17
22
 
23
+ MARSHAL_STACKDEPTH_DEFAULT = 2**9-2
24
+ MARSHAL_STACKDEPTH_MAX_VALUE = 2**10-2
25
+
18
26
  class Error < ::StandardError; end
19
27
 
20
28
  class ContextDisposedError < Error; end
@@ -140,10 +148,10 @@ module MiniRacer
140
148
  end
141
149
  end
142
150
 
143
- 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)
144
152
  options ||= {}
145
153
 
146
- 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)
147
155
 
148
156
  @functions = {}
149
157
  @timeout = nil
@@ -151,6 +159,7 @@ module MiniRacer
151
159
  @current_exception = nil
152
160
  @timeout = timeout
153
161
  @max_memory = max_memory
162
+ @marshal_stack_depth = marshal_stack_depth
154
163
 
155
164
  # false signals it should be fetched if requested
156
165
  @isolate = isolate || false
@@ -198,7 +207,7 @@ module MiniRacer
198
207
  end
199
208
 
200
209
  if !(File === f)
201
- raise ArgumentError("file_or_io")
210
+ raise ArgumentError, "file_or_io"
202
211
  end
203
212
 
204
213
  write_heap_snapshot_unsafe(f)
@@ -345,7 +354,7 @@ module MiniRacer
345
354
 
346
355
  t = Thread.new do
347
356
  begin
348
- result = IO.select([rp],[],[],(@timeout/1000.0))
357
+ result = rp.wait_readable(@timeout/1000.0)
349
358
  if !result
350
359
  mutex.synchronize do
351
360
  stop unless done
@@ -362,7 +371,7 @@ module MiniRacer
362
371
  done = true
363
372
  end
364
373
 
365
- wp.write("done")
374
+ wp.close
366
375
 
367
376
  # ensure we do not leak a thread in state
368
377
  t.join
@@ -371,19 +380,17 @@ module MiniRacer
371
380
  rval
372
381
  ensure
373
382
  # 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
383
+ wp&.close
384
+ t&.join
385
+ rp&.close
380
386
  end
381
387
 
382
- 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:)
383
389
  assert_option_is_nil_or_a('isolate', isolate, Isolate)
384
390
  assert_option_is_nil_or_a('snapshot', snapshot, Snapshot)
385
391
 
386
- 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)
387
394
  assert_numeric_or_nil('ensure_gc_after_idle', ensure_gc_after_idle, min_value: 1)
388
395
  assert_numeric_or_nil('timeout', timeout, min_value: 1)
389
396
 
@@ -392,9 +399,13 @@ module MiniRacer
392
399
  end
393
400
  end
394
401
 
395
- 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
+
396
407
  if object.is_a?(Numeric) && object < min_value
397
- raise ArgumentError, "#{option_name} must be larger than #{min_value}"
408
+ raise ArgumentError, "#{option_name} must be larger than or equal to #{min_value}"
398
409
  end
399
410
 
400
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"
@@ -37,5 +35,5 @@ Gem::Specification.new do |spec|
37
35
 
38
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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-12 00:00:00.000000000 Z
11
+ date: 2022-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 15.14.0.0
89
+ version: 16.10.0.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 15.14.0.0
96
+ version: 16.10.0.0
97
97
  description: Minimal embedded v8 engine for Ruby
98
98
  email:
99
99
  - sam.saffron@gmail.com
@@ -106,7 +106,6 @@ files:
106
106
  - ".dockerignore"
107
107
  - ".github/workflows/ci.yml"
108
108
  - ".gitignore"
109
- - ".travis.yml"
110
109
  - CHANGELOG
111
110
  - CODE_OF_CONDUCT.md
112
111
  - Dockerfile
@@ -121,6 +120,7 @@ files:
121
120
  - ext/mini_racer_loader/extconf.rb
122
121
  - ext/mini_racer_loader/mini_racer_loader.c
123
122
  - lib/mini_racer.rb
123
+ - lib/mini_racer/truffleruby.rb
124
124
  - lib/mini_racer/version.rb
125
125
  - mini_racer.gemspec
126
126
  homepage: https://github.com/discourse/mini_racer
@@ -128,9 +128,9 @@ licenses:
128
128
  - MIT
129
129
  metadata:
130
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
131
+ changelog_uri: https://github.com/discourse/mini_racer/blob/v0.6.3/CHANGELOG
132
+ documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.6.3
133
+ source_code_uri: https://github.com/discourse/mini_racer/tree/v0.6.3
134
134
  post_install_message:
135
135
  rdoc_options: []
136
136
  require_paths:
@@ -140,14 +140,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - ">="
142
142
  - !ruby/object:Gem::Version
143
- version: '2.3'
143
+ version: '2.6'
144
144
  required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  requirements:
146
146
  - - ">="
147
147
  - !ruby/object:Gem::Version
148
148
  version: '0'
149
149
  requirements: []
150
- rubygems_version: 3.2.2
150
+ rubygems_version: 3.3.20
151
151
  signing_key:
152
152
  specification_version: 4
153
153
  summary: Minimal embedded v8 for Ruby
data/.travis.yml DELETED
@@ -1,31 +0,0 @@
1
- language: ruby
2
- os: linux
3
- rvm:
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - 2.7
8
- - 3.0
9
- - ruby-head
10
- arch:
11
- - amd64
12
- - arm64
13
- jobs:
14
- include:
15
- - rvm: 2.5
16
- os: osx
17
- osx_image: xcode9.4
18
- - rvm: 2.6
19
- os: osx
20
- osx_image: xcode11.3
21
- - rvm: 2.6
22
- os: osx
23
- osx_image: xcode12.2
24
- - rvm: 2.7
25
- os: osx
26
- osx_image: xcode12.2
27
- dist: xenial
28
- before_install:
29
- - gem update --system
30
- - gem install bundler -v 1.16.2
31
- cache: bundler