ruby_wasm 2.5.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +8 -0
  3. data/CONTRIBUTING.md +126 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE +21 -0
  6. data/NOTICE +1293 -0
  7. data/README.md +153 -0
  8. data/Rakefile +163 -0
  9. data/Steepfile +24 -0
  10. data/benchmarks/vm_deep_call.rb +55 -0
  11. data/builders/wasm32-unknown-emscripten/Dockerfile +43 -0
  12. data/builders/wasm32-unknown-emscripten/entrypoint.sh +7 -0
  13. data/builders/wasm32-unknown-wasi/Dockerfile +47 -0
  14. data/builders/wasm32-unknown-wasi/entrypoint.sh +7 -0
  15. data/docs/api.md +2 -0
  16. data/docs/cheat_sheet.md +195 -0
  17. data/docs/faq.md +25 -0
  18. data/exe/rbwasm +7 -0
  19. data/ext/.gitignore +2 -0
  20. data/ext/README.md +11 -0
  21. data/ext/extinit.c.erb +32 -0
  22. data/lib/ruby_wasm/3.0/ruby_wasm.so +0 -0
  23. data/lib/ruby_wasm/build/build_params.rb +3 -0
  24. data/lib/ruby_wasm/build/downloader.rb +18 -0
  25. data/lib/ruby_wasm/build/executor.rb +187 -0
  26. data/lib/ruby_wasm/build/product/baseruby.rb +37 -0
  27. data/lib/ruby_wasm/build/product/crossruby.rb +330 -0
  28. data/lib/ruby_wasm/build/product/libyaml.rb +68 -0
  29. data/lib/ruby_wasm/build/product/openssl.rb +88 -0
  30. data/lib/ruby_wasm/build/product/product.rb +39 -0
  31. data/lib/ruby_wasm/build/product/ruby_source.rb +103 -0
  32. data/lib/ruby_wasm/build/product/wasi_vfs.rb +45 -0
  33. data/lib/ruby_wasm/build/product/zlib.rb +68 -0
  34. data/lib/ruby_wasm/build/product.rb +8 -0
  35. data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +31 -0
  36. data/lib/ruby_wasm/build/toolchain.rb +193 -0
  37. data/lib/ruby_wasm/build.rb +88 -0
  38. data/lib/ruby_wasm/cli.rb +217 -0
  39. data/lib/ruby_wasm/packager/core.rb +156 -0
  40. data/lib/ruby_wasm/packager/file_system.rb +158 -0
  41. data/lib/ruby_wasm/packager.rb +159 -0
  42. data/lib/ruby_wasm/rake_task.rb +59 -0
  43. data/lib/ruby_wasm/util.rb +15 -0
  44. data/lib/ruby_wasm/version.rb +3 -0
  45. data/lib/ruby_wasm.rb +33 -0
  46. data/package-lock.json +9500 -0
  47. data/package.json +12 -0
  48. data/rakelib/check.rake +37 -0
  49. data/rakelib/ci.rake +152 -0
  50. data/rakelib/doc.rake +29 -0
  51. data/rakelib/format.rake +35 -0
  52. data/rakelib/gem.rake +22 -0
  53. data/rakelib/packaging.rake +151 -0
  54. data/rakelib/version.rake +40 -0
  55. data/sig/open_uri.rbs +4 -0
  56. data/sig/ruby_wasm/build.rbs +318 -0
  57. data/sig/ruby_wasm/cli.rbs +27 -0
  58. data/sig/ruby_wasm/ext.rbs +13 -0
  59. data/sig/ruby_wasm/packager.rbs +91 -0
  60. data/sig/ruby_wasm/util.rbs +5 -0
  61. data/tools/clang-format-diff.sh +18 -0
  62. data/tools/exe/rbminify +12 -0
  63. data/tools/lib/syntax_tree/minify_ruby.rb +63 -0
  64. metadata +112 -0
@@ -0,0 +1,195 @@
1
+ [[**Cheat Sheet**]](./cheat_sheet.md)
2
+ [[**FAQ**]](./faq.md)
3
+ [[**API Reference**]](./api.md)
4
+ [[**Complete Examples**]](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example)
5
+ [[**Community Showcase**]](https://github.com/ruby/ruby.wasm/wiki/Showcase)
6
+
7
+ # ruby.wasm Cheat Sheet
8
+
9
+ ## Node.js
10
+
11
+ To install the package, install `@ruby/3.3-wasm-wasi` and `@ruby/wasm-wasi` from npm:
12
+
13
+ ```console
14
+ npm install --save @ruby/3.3-wasm-wasi @ruby/wasm-wasi
15
+ ```
16
+
17
+ Then instantiate a Ruby VM by the following code:
18
+
19
+ ```javascript
20
+ import fs from "fs/promises";
21
+ import { DefaultRubyVM } from "@ruby/wasm-wasi/dist/node";
22
+
23
+ const binary = await fs.readFile("./node_modules/@ruby/3.3-wasm-wasi/dist/ruby.wasm");
24
+ const module = await WebAssembly.compile(binary);
25
+ const { vm } = await DefaultRubyVM(module);
26
+ vm.eval(`puts "hello world"`);
27
+ ```
28
+
29
+ Then run the example code with `--experimental-wasi-unstable-preview1` flag to enable WASI support:
30
+
31
+ ```console
32
+ $ node --experimental-wasi-unstable-preview1 index.mjs
33
+ ```
34
+
35
+ ## Browser
36
+
37
+ The easiest way to run Ruby on browser is to use `browser.script.iife.js` script from CDN:
38
+
39
+ ```html
40
+ <html>
41
+ <script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.0/dist/browser.script.iife.js"></script>
42
+ <script type="text/ruby">
43
+ require "js"
44
+ JS.global[:document].write "Hello, world!"
45
+ </script>
46
+ </html>
47
+ ```
48
+
49
+ If you want to control Ruby VM from JavaScript, you can use `@ruby/wasm-wasi` package API:
50
+
51
+ ```html
52
+ <html>
53
+ <script type="module">
54
+ import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.5.0/dist/browser/+esm";
55
+ const response = await fetch("https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.0/dist/ruby+stdlib.wasm");
56
+ const module = await WebAssembly.compileStreaming(response);
57
+ const { vm } = await DefaultRubyVM(module);
58
+
59
+ vm.eval(`
60
+ require "js"
61
+ JS.global[:document].write "Hello, world!"
62
+ `);
63
+ </script>
64
+ </html>
65
+ ```
66
+
67
+ <details>
68
+ <summary>Alternative: Without ES Modules</summary>
69
+
70
+ ```html
71
+ <html>
72
+ <script src="https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.5.0/dist/browser.umd.js"></script>
73
+ <script>
74
+ const main = async () => {
75
+ const { DefaultRubyVM } = window["ruby-wasm-wasi"];
76
+ const response = await fetch("https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.0/dist/ruby+stdlib.wasm");
77
+ const module = await WebAssembly.compileStreaming(response);
78
+ const { vm } = await DefaultRubyVM(module);
79
+
80
+ vm.eval(`
81
+ require "js"
82
+ JS.global[:document].write "Hello, world!"
83
+ `);
84
+ }
85
+ main()
86
+ </script>
87
+ </html>
88
+ ```
89
+ </details>
90
+
91
+ ## Use JavaScript from Ruby
92
+
93
+ ### Get/set JavaScript variables from Ruby
94
+
95
+ ```ruby
96
+ require "js"
97
+
98
+ document = JS.global[:document]
99
+ document[:title] = "Hello, world!"
100
+ ```
101
+
102
+ ### Call JavaScript methods from Ruby
103
+
104
+ ```ruby
105
+ require "js"
106
+
107
+ JS.global[:document].createElement("div")
108
+
109
+ JS.global[:document].call(:createElement, "div".to_js) # same as above
110
+ ```
111
+
112
+ ### Pass Ruby `Proc` to JavaScript (Callback to Ruby)
113
+
114
+ ```ruby
115
+ require "js"
116
+
117
+ JS.global.setTimeout(proc { puts "Hello, world!" }, 1000)
118
+
119
+ input = JS.global[:document].querySelector("input")
120
+ input.addEventListener("change") do |event|
121
+ puts event[:target][:value].to_s
122
+ end
123
+ ```
124
+
125
+ ### `await` JavaScript `Promise` from Ruby
126
+
127
+ `data-eval="async"` attribute is required to use `await` in `<script>` tag:
128
+
129
+ ```html
130
+ <html>
131
+ <script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.0/dist/browser.script.iife.js"></script>
132
+ <script type="text/ruby" data-eval="async">
133
+ require "js"
134
+
135
+ response = JS.global.fetch("https://www.ruby-lang.org/").await
136
+ puts response[:status]
137
+ </script>
138
+ </html>
139
+ ```
140
+
141
+ Or using `@ruby/wasm-wasi` package API `RubyVM#evalAsync`:
142
+
143
+ ```html
144
+ <html>
145
+ <script type="module">
146
+ import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.5.0/dist/browser/+esm";
147
+ const response = await fetch("https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.5.0/dist/ruby+stdlib.wasm");
148
+ const module = await WebAssembly.compileStreaming(response);
149
+ const { vm } = await DefaultRubyVM(module);
150
+
151
+ vm.evalAsync(`
152
+ require "js"
153
+
154
+ response = JS.global.fetch("https://www.ruby-lang.org/").await
155
+ puts response[:status]
156
+ `);
157
+ </script>
158
+ </html>
159
+ ```
160
+
161
+ ### `new` JavaScript instance from Ruby
162
+
163
+ ```ruby
164
+ require "js"
165
+
166
+ JS.global[:Date].new(2000, 9, 13)
167
+ ```
168
+
169
+ ### Convert returned JavaScript `String` value to Ruby `String`
170
+
171
+ ```ruby
172
+ require "js"
173
+
174
+ title = JS.global[:document].title # => JS::Object("Hello, world!")
175
+ title.to_s # => "Hello, world!"
176
+ ```
177
+
178
+ ### Convert JavaScript `Boolean` value to Ruby `true`/`false`
179
+
180
+ ```ruby
181
+ require "js"
182
+
183
+ JS.global[:document].hasFocus? # => true
184
+ JS.global[:document].hasFocus # => JS::Object(true)
185
+ ```
186
+
187
+ ### Convert JavaScript `Number` value to Ruby `Integer`/`Float`
188
+
189
+ ```ruby
190
+ require "js"
191
+
192
+ rand = JS.global[:Math].random # JS::Object(0.123456789)
193
+ rand.to_i # => 0
194
+ rand.to_f # => 0.123456789
195
+ ```
data/docs/faq.md ADDED
@@ -0,0 +1,25 @@
1
+ [[**Cheat Sheet**]](./cheat_sheet.md)
2
+ [[**FAQ**]](./faq.md)
3
+ [[**API Reference**]](./api.md)
4
+ [[**Complete Examples**]](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example)
5
+ [[**Community Showcase**]](https://github.com/ruby/ruby.wasm/wiki/Showcase)
6
+
7
+ # FAQ
8
+
9
+ ## Where my `puts` output goes?
10
+
11
+ By default, `puts` output goes to `STDOUT` which is a JavaScript `console.log` function. You can override it by setting `$stdout` to a Ruby object which has `write` method.
12
+
13
+ ```ruby
14
+ $stdout = Object.new.tap do |obj|
15
+ def obj.write(str)
16
+ JS.global[:document].write(str)
17
+ end
18
+ end
19
+
20
+ puts "Hello, world!" # => Prints "Hello, world!" to the HTML document
21
+ ```
22
+
23
+ ## How to run WebAssembly in Ruby
24
+
25
+ Use [`wasmtime` Ruby gem](https://rubygems.org/gems/wasmtime).
data/exe/rbwasm ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.join(__dir__, "../lib")
4
+ require "ruby_wasm"
5
+ require "ruby_wasm/cli"
6
+
7
+ RubyWasm::CLI.new(stdout: $stdout, stderr: $stderr).run(ARGV)
data/ext/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.o
2
+ link.filelist
data/ext/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Ruby extensions
2
+ `ruby.wasm` uses two C extensions to turn Ruby in to a guest module.
3
+ The `js` extension enables Ruby to use JavaScript APIs.
4
+ The `witapi` extension exports Ruby's interpreter interface to allow the host to use the Ruby interpreter.
5
+ In other words, `js` allows Ruby to talk to Javascript and `witapi` allows a host to talk to Ruby.
6
+
7
+ Under each subdirectory, there is a `bindgen/*.wit` file outlining the interfaces for each form of communication.
8
+ Specifically, `bindgen/rb-js-abi-host.wit` describes embedder's requirements and `bindgen/rb-js-abi-guest.wit` describes exported API from Ruby interpreter.
9
+ The `.c` and `.h` files are autogenerated from [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen#host-runtimes-for-components).
10
+ You can read more about it in the [contributing guide](/CONTRIBUTING.md#re-bindgen-from-wit-files).
11
+ Note that we currently do not use the latest version of wit-bindgen because of how fast it is changing, with features being changed or even [removed](https://github.com/bytecodealliance/wit-bindgen/pull/346) at times.
data/ext/extinit.c.erb ADDED
@@ -0,0 +1,32 @@
1
+ require "erb"
2
+ require "optparse"
3
+
4
+ opts = OptionParser.new
5
+ opts.on("--cc CC") {|cc| @cc = cc }
6
+ opts.on("--output FILE") {|o| @o = o }
7
+
8
+ opts.parse!(ARGV)
9
+ if @cc.nil? || @o.nil?
10
+ puts opts.help
11
+ exit 1
12
+ end
13
+
14
+ exts = ARGV
15
+
16
+ c_src = ERB.new(DATA.read).result
17
+ IO.popen([@cc, "-c", "-xc", "-", "-o", @o], "w") {|f| f << c_src }
18
+ exit $?.exitstatus
19
+
20
+ __END__
21
+ #define init(func, name) { \
22
+ extern void func(void); \
23
+ ruby_init_ext(name".so", func); \
24
+ }
25
+
26
+ void ruby_init_ext(const char *name, void (*init)(void));
27
+
28
+ void Init_extra_exts(void) {
29
+ <% exts.each do |ext| %>
30
+ init(<%= "Init_#{File.basename ext}" %>, "<%= ext %>");
31
+ <% end %>
32
+ }
Binary file
@@ -0,0 +1,3 @@
1
+ module RubyWasm
2
+ BuildParams = Struct.new(:name, :target, :default_exts, keyword_init: true)
3
+ end
@@ -0,0 +1,18 @@
1
+ module RubyWasm
2
+ class Downloader
3
+ def download(url, dest, message)
4
+ require "open-uri"
5
+ content_length = 0
6
+ uri = URI.parse(url)
7
+ OpenURI.open_uri(
8
+ uri,
9
+ content_length_proc: ->(len) { content_length = len },
10
+ progress_proc: ->(size) do
11
+ print "\r#{message} (#{SizeFormatter.format(content_length)}) %.2f%%" %
12
+ (size.to_f / content_length * 100)
13
+ end
14
+ ) { |f| File.open(dest, "wb") { |out| out.write f.read } }
15
+ puts "\r"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,187 @@
1
+ module RubyWasm
2
+ # Build executor to run the actual build commands.
3
+ class BuildExecutor
4
+ attr_reader :process_count
5
+
6
+ def initialize(verbose: false, process_count: nil)
7
+ @verbose = verbose
8
+ @github_actions_markup = ENV["ENABLE_GITHUB_ACTIONS_MARKUP"] != nil
9
+ __skip__ =
10
+ begin
11
+ require "etc"
12
+ @process_count = process_count || Etc.nprocessors
13
+ rescue LoadError
14
+ @process_count = process_count || 1
15
+ end
16
+ end
17
+
18
+ def system(*args, chdir: nil, env: nil)
19
+ require "open3"
20
+
21
+ _print_command(args, env)
22
+
23
+ # @type var kwargs: Hash[Symbol, untyped]
24
+ kwargs = {}
25
+ kwargs[:chdir] = chdir if chdir
26
+
27
+ args = args.to_a.map(&:to_s)
28
+ # TODO: Remove __skip__ once we have open3 RBS definitions.
29
+ __skip__ =
30
+ if @verbose || !$stdout.tty?
31
+ kwargs[:exception] = true
32
+ if env
33
+ Kernel.system(env, *args, **kwargs)
34
+ else
35
+ Kernel.system(*args, **kwargs)
36
+ end
37
+ else
38
+ printer = StatusPrinter.new
39
+ block =
40
+ proc do |stdin, stdout, stderr, wait_thr|
41
+ mux = Mutex.new
42
+ out = String.new
43
+ err = String.new
44
+ readers =
45
+ [
46
+ [stdout, :stdout, out],
47
+ [stderr, :stderr, err]
48
+ ].map do |io, name, str|
49
+ reader =
50
+ Thread.new do
51
+ while (line = io.gets)
52
+ mux.synchronize do
53
+ printer.send(name, line)
54
+ str << line
55
+ end
56
+ end
57
+ end
58
+ reader.report_on_exception = false
59
+ reader
60
+ end
61
+
62
+ readers.each(&:join)
63
+
64
+ [out, err, wait_thr.value]
65
+ end
66
+ begin
67
+ stdout, stderr, status =
68
+ if env
69
+ Open3.popen3(env, *args, **kwargs, &block)
70
+ else
71
+ Open3.popen3(*args, **kwargs, &block)
72
+ end
73
+ unless status.success?
74
+ $stderr.puts stdout
75
+ $stderr.puts stderr
76
+ cmd_to_print = args.map { |a| "'#{a}'" }.join(" ")
77
+ raise "Command failed with status (#{status.exitstatus}): #{cmd_to_print}"
78
+ end
79
+ ensure
80
+ printer.done
81
+ end
82
+ end
83
+ return
84
+ rescue => e
85
+ $stdout.flush
86
+ $stderr.puts "Try running with `rake --verbose` for more complete output."
87
+ raise e
88
+ end
89
+
90
+ def begin_section(klass, name, note)
91
+ message = "\e[1;36m==>\e[0m \e[1m#{klass}(#{name}) -- #{note}\e[0m"
92
+ if @github_actions_markup
93
+ puts "::group::#{message}"
94
+ else
95
+ puts message
96
+ end
97
+
98
+ # Record the start time
99
+ @start_times ||= Hash.new
100
+ @start_times[[klass, name]] = Time.now
101
+
102
+ $stdout.flush
103
+ end
104
+
105
+ def end_section(klass, name)
106
+ took = Time.now - @start_times[[klass, name]]
107
+ puts "::endgroup::" if @github_actions_markup
108
+ puts "\e[1;36m==>\e[0m \e[1m#{klass}(#{name}) -- done in #{took.round(2)}s\e[0m"
109
+ end
110
+
111
+ def rm_rf(list)
112
+ FileUtils.rm_rf(list)
113
+ end
114
+
115
+ def rm_f(list)
116
+ FileUtils.rm_f(list)
117
+ end
118
+
119
+ def cp_r(src, dest)
120
+ FileUtils.cp_r(src, dest)
121
+ end
122
+
123
+ def mv(src, dest)
124
+ FileUtils.mv(src, dest)
125
+ end
126
+
127
+ def mkdir_p(list)
128
+ FileUtils.mkdir_p(list)
129
+ end
130
+
131
+ def write(path, data)
132
+ File.write(path, data)
133
+ end
134
+
135
+ private
136
+
137
+ def _print_command(args, env)
138
+ require "shellwords"
139
+ # Bold cyan
140
+ print "\e[1;36m ==>\e[0m "
141
+ print "env " + env.map { |k, v| "#{k}=#{v}" }.join(" ") + " " if env
142
+ print args.map { |arg| Shellwords.escape(arg.to_s) }.join(" ") + "\n"
143
+ end
144
+ end
145
+
146
+ # Human readable status printer for the build.
147
+ class StatusPrinter
148
+ def initialize
149
+ @mutex = Mutex.new
150
+ @counter = 0
151
+ @indicators = "|/-\\"
152
+ end
153
+
154
+ def stdout(message)
155
+ require "io/console"
156
+ @mutex.synchronize do
157
+ $stdout.print "\e[K"
158
+ first_line = message.lines(chomp: true).first || ""
159
+
160
+ # Make sure we don't line-wrap the output
161
+ size =
162
+ __skip__ =
163
+ IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize
164
+ terminal_width = size[1].to_i.nonzero? || 80
165
+ width_limit = terminal_width / 2 - 3
166
+
167
+ if first_line.length > width_limit
168
+ first_line = (first_line[0..width_limit - 5] || "") + "..."
169
+ end
170
+ indicator = @indicators[@counter] || " "
171
+ to_print = " " + indicator + " " + first_line
172
+ $stdout.print to_print
173
+ $stdout.print "\e[1A\n"
174
+ @counter += 1
175
+ @counter = 0 if @counter >= @indicators.length
176
+ end
177
+ end
178
+
179
+ def stderr(message)
180
+ @mutex.synchronize { $stdout.print message }
181
+ end
182
+
183
+ def done
184
+ @mutex.synchronize { $stdout.print "\e[K" }
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "./product"
2
+
3
+ module RubyWasm
4
+ class BaseRubyProduct < BuildProduct
5
+ def initialize(build_dir, source)
6
+ @build_dir = build_dir
7
+ @source = source
8
+ @channel = source.name
9
+ end
10
+
11
+ def product_build_dir
12
+ File.join(@build_dir, RbConfig::CONFIG["host"], "baseruby-#{@channel}")
13
+ end
14
+
15
+ def install_dir
16
+ File.join(product_build_dir, "opt")
17
+ end
18
+
19
+ def name
20
+ "baseruby-#{@channel}"
21
+ end
22
+
23
+ def build(executor)
24
+ executor.mkdir_p product_build_dir
25
+ @source.build(executor)
26
+ return if Dir.exist?(install_dir)
27
+ executor.system @source.configure_file,
28
+ "--prefix=#{install_dir}",
29
+ "--disable-install-doc",
30
+ chdir: product_build_dir
31
+ executor.system "make",
32
+ "-j#{executor.process_count}",
33
+ "install",
34
+ chdir: product_build_dir
35
+ end
36
+ end
37
+ end