ruby_wasm 2.5.0-x64-mingw-ucrt

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.
Files changed (65) 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.1/ruby_wasm.so +0 -0
  23. data/lib/ruby_wasm/3.2/ruby_wasm.so +0 -0
  24. data/lib/ruby_wasm/build/build_params.rb +3 -0
  25. data/lib/ruby_wasm/build/downloader.rb +18 -0
  26. data/lib/ruby_wasm/build/executor.rb +187 -0
  27. data/lib/ruby_wasm/build/product/baseruby.rb +37 -0
  28. data/lib/ruby_wasm/build/product/crossruby.rb +330 -0
  29. data/lib/ruby_wasm/build/product/libyaml.rb +68 -0
  30. data/lib/ruby_wasm/build/product/openssl.rb +88 -0
  31. data/lib/ruby_wasm/build/product/product.rb +39 -0
  32. data/lib/ruby_wasm/build/product/ruby_source.rb +103 -0
  33. data/lib/ruby_wasm/build/product/wasi_vfs.rb +45 -0
  34. data/lib/ruby_wasm/build/product/zlib.rb +68 -0
  35. data/lib/ruby_wasm/build/product.rb +8 -0
  36. data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +31 -0
  37. data/lib/ruby_wasm/build/toolchain.rb +193 -0
  38. data/lib/ruby_wasm/build.rb +88 -0
  39. data/lib/ruby_wasm/cli.rb +217 -0
  40. data/lib/ruby_wasm/packager/core.rb +156 -0
  41. data/lib/ruby_wasm/packager/file_system.rb +158 -0
  42. data/lib/ruby_wasm/packager.rb +159 -0
  43. data/lib/ruby_wasm/rake_task.rb +59 -0
  44. data/lib/ruby_wasm/util.rb +15 -0
  45. data/lib/ruby_wasm/version.rb +3 -0
  46. data/lib/ruby_wasm.rb +33 -0
  47. data/package-lock.json +9500 -0
  48. data/package.json +12 -0
  49. data/rakelib/check.rake +37 -0
  50. data/rakelib/ci.rake +152 -0
  51. data/rakelib/doc.rake +29 -0
  52. data/rakelib/format.rake +35 -0
  53. data/rakelib/gem.rake +22 -0
  54. data/rakelib/packaging.rake +151 -0
  55. data/rakelib/version.rake +40 -0
  56. data/sig/open_uri.rbs +4 -0
  57. data/sig/ruby_wasm/build.rbs +318 -0
  58. data/sig/ruby_wasm/cli.rbs +27 -0
  59. data/sig/ruby_wasm/ext.rbs +13 -0
  60. data/sig/ruby_wasm/packager.rbs +91 -0
  61. data/sig/ruby_wasm/util.rbs +5 -0
  62. data/tools/clang-format-diff.sh +18 -0
  63. data/tools/exe/rbminify +12 -0
  64. data/tools/lib/syntax_tree/minify_ruby.rb +63 -0
  65. metadata +113 -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
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