ruby_wasm 2.5.0.pre.1

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +8 -0
  3. data/CONTRIBUTING.md +124 -0
  4. data/Cargo.lock +2452 -0
  5. data/Cargo.toml +7 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE +21 -0
  8. data/NOTICE +1293 -0
  9. data/README.md +161 -0
  10. data/Rakefile +164 -0
  11. data/Steepfile +24 -0
  12. data/benchmarks/vm_deep_call.rb +55 -0
  13. data/builders/wasm32-unknown-emscripten/Dockerfile +43 -0
  14. data/builders/wasm32-unknown-emscripten/entrypoint.sh +7 -0
  15. data/builders/wasm32-unknown-wasi/Dockerfile +47 -0
  16. data/builders/wasm32-unknown-wasi/entrypoint.sh +7 -0
  17. data/docs/api.md +2 -0
  18. data/docs/cheat_sheet.md +195 -0
  19. data/docs/faq.md +25 -0
  20. data/exe/rbwasm +7 -0
  21. data/ext/.gitignore +2 -0
  22. data/ext/README.md +11 -0
  23. data/ext/extinit.c.erb +32 -0
  24. data/ext/ruby_wasm/Cargo.toml +17 -0
  25. data/ext/ruby_wasm/extconf.rb +6 -0
  26. data/ext/ruby_wasm/src/lib.rs +69 -0
  27. data/lib/ruby_wasm/build/build_params.rb +3 -0
  28. data/lib/ruby_wasm/build/downloader.rb +18 -0
  29. data/lib/ruby_wasm/build/executor.rb +187 -0
  30. data/lib/ruby_wasm/build/product/baseruby.rb +37 -0
  31. data/lib/ruby_wasm/build/product/crossruby.rb +326 -0
  32. data/lib/ruby_wasm/build/product/libyaml.rb +68 -0
  33. data/lib/ruby_wasm/build/product/openssl.rb +88 -0
  34. data/lib/ruby_wasm/build/product/product.rb +39 -0
  35. data/lib/ruby_wasm/build/product/ruby_source.rb +103 -0
  36. data/lib/ruby_wasm/build/product/wasi_vfs.rb +83 -0
  37. data/lib/ruby_wasm/build/product/zlib.rb +68 -0
  38. data/lib/ruby_wasm/build/product.rb +8 -0
  39. data/lib/ruby_wasm/build/toolchain/wit_bindgen.rb +31 -0
  40. data/lib/ruby_wasm/build/toolchain.rb +193 -0
  41. data/lib/ruby_wasm/build.rb +88 -0
  42. data/lib/ruby_wasm/cli.rb +195 -0
  43. data/lib/ruby_wasm/packager/core.rb +156 -0
  44. data/lib/ruby_wasm/packager/file_system.rb +157 -0
  45. data/lib/ruby_wasm/packager.rb +159 -0
  46. data/lib/ruby_wasm/rake_task.rb +58 -0
  47. data/lib/ruby_wasm/util.rb +15 -0
  48. data/lib/ruby_wasm/version.rb +3 -0
  49. data/lib/ruby_wasm.rb +33 -0
  50. data/package-lock.json +9500 -0
  51. data/package.json +12 -0
  52. data/ruby_wasm.gemspec +32 -0
  53. data/sig/open_uri.rbs +4 -0
  54. data/sig/ruby_wasm/build.rbs +322 -0
  55. data/sig/ruby_wasm/cli.rbs +24 -0
  56. data/sig/ruby_wasm/ext.rbs +11 -0
  57. data/sig/ruby_wasm/packager.rbs +91 -0
  58. data/sig/ruby_wasm/util.rbs +5 -0
  59. data/tasks/check.rake +37 -0
  60. data/tasks/ci.rake +152 -0
  61. data/tasks/doc.rake +24 -0
  62. data/tasks/format.rake +34 -0
  63. data/tasks/gem.rake +19 -0
  64. data/tasks/packaging.rake +148 -0
  65. data/tasks/version.rake +38 -0
  66. data/tools/clang-format-diff.sh +18 -0
  67. data/tools/exe/rbminify +12 -0
  68. data/tools/lib/syntax_tree/minify_ruby.rb +63 -0
  69. metadata +115 -0
@@ -0,0 +1,68 @@
1
+ require_relative "./product"
2
+
3
+ module RubyWasm
4
+ class ZlibProduct < AutoconfProduct
5
+ attr_reader :target
6
+
7
+ ZLIB_VERSION = "1.3"
8
+
9
+ def initialize(build_dir, target, toolchain)
10
+ @build_dir = build_dir
11
+ @target = target
12
+ super(target, toolchain)
13
+ end
14
+
15
+ def product_build_dir
16
+ File.join(@build_dir, target, "zlib-#{ZLIB_VERSION}")
17
+ end
18
+
19
+ def destdir
20
+ File.join(product_build_dir, "opt")
21
+ end
22
+
23
+ def install_root
24
+ File.join(destdir, "usr", "local")
25
+ end
26
+
27
+ def name
28
+ "zlib-#{ZLIB_VERSION}-#{target}"
29
+ end
30
+
31
+ def configure_args
32
+ args = %w[CHOST=linux]
33
+
34
+ args + tools_args
35
+ end
36
+
37
+ def build(executor)
38
+ return if Dir.exist?(install_root)
39
+
40
+ executor.mkdir_p File.dirname(product_build_dir)
41
+ executor.rm_rf product_build_dir
42
+ executor.mkdir_p product_build_dir
43
+
44
+ tarball_path = File.join(product_build_dir, "zlib-#{ZLIB_VERSION}.tar.gz")
45
+ executor.system "curl",
46
+ "-o",
47
+ tarball_path,
48
+ "-L",
49
+ "https://zlib.net/zlib-#{ZLIB_VERSION}.tar.gz"
50
+ executor.system "tar",
51
+ "xzf",
52
+ tarball_path,
53
+ "-C",
54
+ product_build_dir,
55
+ "--strip-components=1"
56
+
57
+ executor.system "env",
58
+ *configure_args,
59
+ "./configure",
60
+ "--static",
61
+ chdir: product_build_dir
62
+ executor.system "make",
63
+ "install",
64
+ "DESTDIR=#{destdir}",
65
+ chdir: product_build_dir
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "product/product"
2
+ require_relative "product/ruby_source"
3
+ require_relative "product/baseruby"
4
+ require_relative "product/zlib"
5
+ require_relative "product/libyaml"
6
+ require_relative "product/openssl"
7
+ require_relative "product/wasi_vfs"
8
+ require_relative "product/crossruby"
@@ -0,0 +1,31 @@
1
+ module RubyWasm
2
+ class WitBindgen
3
+ attr_reader :bin_path
4
+
5
+ def initialize(
6
+ build_dir:,
7
+ revision: "251e84b89121751f79ac268629e9285082b2596d"
8
+ )
9
+ @build_dir = build_dir
10
+ @tool_dir = File.join(@build_dir, "toolchain", "wit-bindgen")
11
+ @bin_path = File.join(@tool_dir, "bin", "wit-bindgen")
12
+ @revision = revision
13
+ end
14
+
15
+ def install
16
+ return if File.exist?(@bin_path)
17
+ RubyWasm::Toolchain.check_executable("cargo")
18
+ Kernel.system(
19
+ "cargo",
20
+ "install",
21
+ "--git",
22
+ "https://github.com/bytecodealliance/wit-bindgen",
23
+ "--rev",
24
+ @revision,
25
+ "--root",
26
+ @tool_dir,
27
+ "wit-bindgen-cli"
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,193 @@
1
+ require_relative "./toolchain/wit_bindgen"
2
+
3
+ module RubyWasm
4
+ class Toolchain
5
+ attr_reader :name
6
+
7
+ def initialize
8
+ @tools = {}
9
+ end
10
+
11
+ def find_tool(name)
12
+ raise "not implemented"
13
+ end
14
+
15
+ def check_envvar(name)
16
+ raise "missing environment variable: #{name}" if ENV[name].nil?
17
+ end
18
+
19
+ def self.get(target, build_dir = nil)
20
+ case target
21
+ when "wasm32-unknown-wasi"
22
+ return RubyWasm::WASISDK.new(build_dir: build_dir)
23
+ when "wasm32-unknown-emscripten"
24
+ return RubyWasm::Emscripten.new
25
+ else
26
+ raise "unknown target: #{target}"
27
+ end
28
+ end
29
+
30
+ def self.find_path(command)
31
+ (ENV["PATH"] || "")
32
+ .split(File::PATH_SEPARATOR)
33
+ .each do |path_dir|
34
+ bin_path = File.join(path_dir, command)
35
+ return bin_path if File.executable?(bin_path)
36
+ end
37
+ nil
38
+ end
39
+
40
+ def self.check_executable(command)
41
+ tool = find_path(command)
42
+ raise "missing executable: #{command}" unless tool
43
+ tool
44
+ end
45
+
46
+ %i[cc ranlib ld ar].each do |name|
47
+ define_method(name) do
48
+ @tools_cache ||= {}
49
+ @tools_cache[name] ||= find_tool(name)
50
+ @tools_cache[name]
51
+ end
52
+ end
53
+ end
54
+
55
+ class WASISDK < Toolchain
56
+ def initialize(
57
+ wasi_sdk_path = ENV["WASI_SDK_PATH"],
58
+ build_dir: nil,
59
+ version_major: 20,
60
+ version_minor: 0,
61
+ binaryen_version: 108
62
+ )
63
+ @wasm_opt_path = Toolchain.find_path("wasm-opt")
64
+ @need_fetch_wasi_sdk = wasi_sdk_path.nil?
65
+ @need_fetch_binaryen = @wasm_opt_path.nil?
66
+
67
+ if @need_fetch_wasi_sdk
68
+ if build_dir.nil?
69
+ raise "build_dir is required when WASI_SDK_PATH is not set"
70
+ end
71
+ wasi_sdk_path = File.join(build_dir, "toolchain", "wasi-sdk")
72
+ @version_major = version_major
73
+ @version_minor = version_minor
74
+ end
75
+
76
+ if @need_fetch_binaryen
77
+ if build_dir.nil?
78
+ raise "build_dir is required when wasm-opt not installed in PATH"
79
+ end
80
+ @binaryen_path = File.join(build_dir, "toolchain", "binaryen")
81
+ @binaryen_version = binaryen_version
82
+ @wasm_opt_path = File.join(@binaryen_path, "bin", "wasm-opt")
83
+ end
84
+
85
+ @tools = {
86
+ cc: "#{wasi_sdk_path}/bin/clang",
87
+ ld: "#{wasi_sdk_path}/bin/clang",
88
+ ar: "#{wasi_sdk_path}/bin/llvm-ar",
89
+ ranlib: "#{wasi_sdk_path}/bin/llvm-ranlib"
90
+ }
91
+ @wasi_sdk_path = wasi_sdk_path
92
+ @name = "wasi-sdk"
93
+ end
94
+
95
+ def find_tool(name)
96
+ if !File.exist?(@tools[name]) && !ENV["WASI_SDK_PATH"].nil?
97
+ raise "missing tool '#{name}' at #{@tools[name]}"
98
+ end
99
+ @tools[name]
100
+ end
101
+
102
+ def wasm_opt
103
+ @wasm_opt_path
104
+ end
105
+
106
+ def wasi_sdk_path
107
+ @wasi_sdk_path
108
+ end
109
+
110
+ def download_url(version_major, version_minor)
111
+ version = "#{version_major}.#{version_minor}"
112
+ assets = [
113
+ [/x86_64-linux/, "wasi-sdk-#{version}-linux.tar.gz"],
114
+ [/(arm64e?|x86_64)-darwin/, "wasi-sdk-#{version}-macos.tar.gz"]
115
+ ]
116
+ asset = assets.find { |os, _| os =~ RUBY_PLATFORM }&.at(1)
117
+ if asset.nil?
118
+ raise "unsupported platform for fetching WASI SDK: #{RUBY_PLATFORM}"
119
+ end
120
+ "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-#{version_major}/#{asset}"
121
+ end
122
+
123
+ def binaryen_download_url(version)
124
+ assets = [
125
+ [
126
+ /x86_64-linux/,
127
+ "binaryen-version_#{@binaryen_version}-x86_64-linux.tar.gz"
128
+ ],
129
+ [
130
+ /x86_64-darwin/,
131
+ "binaryen-version_#{@binaryen_version}-x86_64-macos.tar.gz"
132
+ ],
133
+ [
134
+ /arm64e?-darwin/,
135
+ "binaryen-version_#{@binaryen_version}-arm64-macos.tar.gz"
136
+ ]
137
+ ]
138
+ asset = assets.find { |os, _| os =~ RUBY_PLATFORM }&.at(1)
139
+ if asset.nil?
140
+ raise "unsupported platform for fetching Binaryen: #{RUBY_PLATFORM}"
141
+ end
142
+ "https://github.com/WebAssembly/binaryen/releases/download/version_#{@binaryen_version}/#{asset}"
143
+ end
144
+
145
+ def install_wasi_sdk
146
+ return unless @need_fetch_wasi_sdk
147
+ wasi_sdk_tarball =
148
+ File.join(File.dirname(@wasi_sdk_path), "wasi-sdk.tar.gz")
149
+ unless File.exist? wasi_sdk_tarball
150
+ FileUtils.mkdir_p File.dirname(wasi_sdk_tarball)
151
+ system "curl -L -o #{wasi_sdk_tarball} #{self.download_url(@version_major, @version_minor)}"
152
+ end
153
+ unless File.exist? @wasi_sdk_path
154
+ FileUtils.mkdir_p @wasi_sdk_path
155
+ system "tar -C #{@wasi_sdk_path} --strip-component 1 -xzf #{wasi_sdk_tarball}"
156
+ end
157
+ end
158
+
159
+ def install_binaryen
160
+ return unless @need_fetch_binaryen
161
+ binaryen_tarball = File.expand_path("../binaryen.tar.gz", @binaryen_path)
162
+ unless File.exist? binaryen_tarball
163
+ FileUtils.mkdir_p File.dirname(binaryen_tarball)
164
+ system "curl -L -o #{binaryen_tarball} #{self.binaryen_download_url(@binaryen_version)}"
165
+ end
166
+
167
+ unless File.exist? @binaryen_path
168
+ FileUtils.mkdir_p @binaryen_path
169
+ system "tar -C #{@binaryen_path} --strip-component 1 -xzf #{binaryen_tarball}"
170
+ end
171
+ end
172
+
173
+ def install
174
+ install_wasi_sdk
175
+ install_binaryen
176
+ end
177
+ end
178
+
179
+ class Emscripten < Toolchain
180
+ def initialize
181
+ @tools = { cc: "emcc", ld: "emcc", ar: "emar", ranlib: "emranlib" }
182
+ @name = "emscripten"
183
+ end
184
+
185
+ def install
186
+ end
187
+
188
+ def find_tool(name)
189
+ Toolchain.check_executable(@tools[name])
190
+ @tools[name]
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,88 @@
1
+ require_relative "build/build_params"
2
+ require_relative "build/product"
3
+ require_relative "build/toolchain"
4
+ require_relative "build/executor"
5
+
6
+ class RubyWasm::Build
7
+ # Source to build from.
8
+ attr_reader :source
9
+
10
+ # Target to build for.
11
+ attr_reader :target
12
+
13
+ # Toolchain for the build.
14
+ # Defaults to the Toolchain.get for the target.
15
+ attr_reader :toolchain
16
+
17
+ # LibYAML product to build.
18
+ attr_reader :libyaml
19
+
20
+ # zlib product to build.
21
+ attr_reader :zlib
22
+
23
+ # wasi-vfs product used by the crossruby.
24
+ attr_reader :wasi_vfs
25
+
26
+ # BaseRuby product to build.
27
+ attr_reader :baseruby
28
+
29
+ # CrossRuby product to build.
30
+ attr_reader :crossruby
31
+
32
+ def initialize(
33
+ name,
34
+ target:,
35
+ src:,
36
+ toolchain:,
37
+ build_dir:,
38
+ rubies_dir:,
39
+ **options
40
+ )
41
+ @target = target
42
+ @build_dir = build_dir
43
+ @rubies_dir = rubies_dir
44
+ @toolchain = (toolchain || RubyWasm::Toolchain.get(target, @build_dir))
45
+
46
+ @libyaml = RubyWasm::LibYAMLProduct.new(@build_dir, @target, @toolchain)
47
+ @zlib = RubyWasm::ZlibProduct.new(@build_dir, @target, @toolchain)
48
+ @wasi_vfs = RubyWasm::WasiVfsProduct.new(@build_dir)
49
+ @source = RubyWasm::BuildSource.new(src, @build_dir)
50
+ @baseruby = RubyWasm::BaseRubyProduct.new(@build_dir, @source)
51
+ @openssl = RubyWasm::OpenSSLProduct.new(@build_dir, @target, @toolchain)
52
+
53
+ build_params =
54
+ RubyWasm::BuildParams.new(
55
+ name: name,
56
+ target: target,
57
+ default_exts: options[:default_exts]
58
+ )
59
+
60
+ @crossruby =
61
+ RubyWasm::CrossRubyProduct.new(
62
+ build_params,
63
+ @build_dir,
64
+ @rubies_dir,
65
+ @baseruby,
66
+ @source,
67
+ @toolchain
68
+ )
69
+
70
+ @crossruby.with_libyaml @libyaml
71
+ @crossruby.with_zlib @zlib
72
+ @crossruby.with_wasi_vfs @wasi_vfs
73
+ @crossruby.with_openssl @openssl
74
+ end
75
+
76
+ def cache_key(digest)
77
+ @source.cache_key(digest)
78
+ @crossruby.cache_key(digest)
79
+ digest << @build_dir
80
+ digest << @rubies_dir
81
+ digest << @target
82
+ digest << @toolchain.name
83
+ digest << @libyaml.name
84
+ digest << @zlib.name
85
+ digest << @openssl.name
86
+ digest << @wasi_vfs.name
87
+ end
88
+ end
@@ -0,0 +1,195 @@
1
+ require "optparse"
2
+ require "rbconfig"
3
+
4
+ module RubyWasm
5
+ class CLI
6
+ def initialize(stdout:, stderr:)
7
+ @stdout = stdout
8
+ @stderr = stderr
9
+ end
10
+
11
+ def run(args)
12
+ available_commands = %w[build]
13
+ parser =
14
+ OptionParser.new do |opts|
15
+ opts.banner = <<~USAGE
16
+ Usage: rbwasm [options...] [command]
17
+
18
+ Available commands: #{available_commands.join(", ")}
19
+ USAGE
20
+ opts.version = RubyWasm::VERSION
21
+ opts.on("-h", "--help", "Prints this help") do
22
+ @stderr.puts opts
23
+ exit
24
+ end
25
+ opts.on("--log-level LEVEL", "Log level") do |level|
26
+ RubyWasm.log_level = level.to_sym
27
+ end
28
+ end
29
+ parser.order!(args)
30
+
31
+ command = args.shift
32
+ case command
33
+ when "build"
34
+ build(args)
35
+ else
36
+ @stderr.puts parser
37
+ exit
38
+ end
39
+ end
40
+
41
+ def build(args)
42
+ # @type var options: Hash[Symbol, untyped]
43
+ options = {
44
+ save_temps: false,
45
+ optimize: false,
46
+ remake: false,
47
+ reconfigure: false,
48
+ clean: false,
49
+ ruby_version: "3.3",
50
+ target_triplet: "wasm32-unknown-wasi",
51
+ profile: "full",
52
+ stdlib: true,
53
+ disable_gems: false
54
+ }
55
+ OptionParser
56
+ .new do |opts|
57
+ opts.banner = "Usage: rbwasm componentize [options]"
58
+ opts.on("-h", "--help", "Prints this help") do
59
+ @stdout.puts opts
60
+ exit
61
+ end
62
+
63
+ opts.on("--save-temps", "Save temporary files") do
64
+ options[:save_temps] = true
65
+ end
66
+
67
+ opts.on("--ruby-version VERSION", "Ruby version") do |version|
68
+ options[:ruby_version] = version
69
+ end
70
+
71
+ opts.on("--target TRIPLET", "Target triplet") do |triplet|
72
+ options[:target_triplet] = triplet
73
+ end
74
+
75
+ opts.on(
76
+ "--build-profile PROFILE",
77
+ "Build profile. full or minimal"
78
+ ) { |profile| options[:profile] = profile }
79
+
80
+ opts.on("--optimize", "Optimize the output") do
81
+ options[:optimize] = true
82
+ end
83
+
84
+ opts.on("--remake", "Re-execute make for Ruby") do
85
+ options[:remake] = true
86
+ end
87
+
88
+ opts.on("--reconfigure", "Re-execute configure for Ruby") do
89
+ options[:reconfigure] = true
90
+ end
91
+
92
+ opts.on("--clean", "Clean build artifacts") { options[:clean] = true }
93
+
94
+ opts.on("-o", "--output FILE", "Output file") do |file|
95
+ options[:output] = file
96
+ end
97
+
98
+ opts.on("--[no-]stdlib", "Include stdlib") do |stdlib|
99
+ options[:stdlib] = stdlib
100
+ end
101
+
102
+ opts.on("--disable-gems", "Disable gems") do
103
+ options[:disable_gems] = true
104
+ end
105
+
106
+ opts.on("--format FORMAT", "Output format") do |format|
107
+ options[:format] = format
108
+ end
109
+
110
+ opts.on("--print-ruby-cache-key", "Print Ruby cache key") do
111
+ options[:print_ruby_cache_key] = true
112
+ end
113
+ end
114
+ .parse!(args)
115
+
116
+ verbose = RubyWasm.logger.level == :debug
117
+ executor = RubyWasm::BuildExecutor.new(verbose: verbose)
118
+ packager = self.derive_packager(options)
119
+
120
+ if options[:print_ruby_cache_key]
121
+ self.do_print_ruby_cache_key(packager)
122
+ exit
123
+ end
124
+
125
+ unless options[:output]
126
+ @stderr.puts "Output file is not specified"
127
+ exit 1
128
+ end
129
+
130
+ require "tmpdir"
131
+
132
+ if options[:save_temps]
133
+ tmpdir = Dir.mktmpdir
134
+ self.do_build(executor, tmpdir, packager, options)
135
+ @stderr.puts "Temporary files are saved to #{tmpdir}"
136
+ exit
137
+ else
138
+ Dir.mktmpdir do |tmpdir|
139
+ self.do_build(executor, tmpdir, packager, options)
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def build_config(options)
147
+ config = { target: options[:target_triplet], src: options[:ruby_version] }
148
+ case options[:profile]
149
+ when "full"
150
+ config[:default_exts] = RubyWasm::Packager::ALL_DEFAULT_EXTS
151
+ when "minimal"
152
+ config[:default_exts] = ""
153
+ else
154
+ RubyWasm.logger.error "Unknown profile: #{options[:profile]} (available: full, minimal)"
155
+ exit 1
156
+ end
157
+ config[:suffix] = "-#{options[:profile]}"
158
+ config
159
+ end
160
+
161
+ def derive_packager(options)
162
+ __skip__ =
163
+ if defined?(Bundler) && !options[:disable_gems]
164
+ definition = Bundler.definition
165
+ end
166
+ RubyWasm::Packager.new(build_config(options), definition)
167
+ end
168
+
169
+ def do_print_ruby_cache_key(packager)
170
+ ruby_core_build = packager.ruby_core_build
171
+ require "digest"
172
+ digest = Digest::SHA256.new
173
+ ruby_core_build.cache_key(digest)
174
+ hexdigest = digest.hexdigest
175
+ require "json"
176
+ @stdout.puts JSON.generate(
177
+ hexdigest: hexdigest,
178
+ artifact: ruby_core_build.artifact
179
+ )
180
+ end
181
+
182
+ def do_build(executor, tmpdir, packager, options)
183
+ require_relative "ruby_wasm.so"
184
+ wasm_bytes = packager.package(executor, tmpdir, options)
185
+ RubyWasm.logger.info "Size: #{SizeFormatter.format(wasm_bytes.size)}"
186
+ case options[:output]
187
+ when "-"
188
+ @stdout.write wasm_bytes.pack("C*")
189
+ else
190
+ File.binwrite(options[:output], wasm_bytes.pack("C*"))
191
+ RubyWasm.logger.debug "Wrote #{options[:output]}"
192
+ end
193
+ end
194
+ end
195
+ end