Class: RubyWasm::CLI
- Inherits:
-
Object
- Object
- RubyWasm::CLI
- Defined in:
- lib/ruby_wasm/cli.rb,
sig/ruby_wasm/cli.rbs
Constant Summary collapse
- DEFAULT_RUBIES_DIR =
Class Method Summary collapse
-
.build_config_aliases(root) ⇒ Hash[string, Packager::build_config]
Retrieves the alias definitions for the Ruby sources.
-
.bundled_patches_path ⇒ string
Path to the directory containing the bundled patches, which is shipped as part of ruby_wasm gem to backport fixes or try experimental features before landing them to the ruby/ruby repository.
Instance Method Summary collapse
- #build(args) ⇒ void
- #build_config(options) ⇒ Packager::build_config
- #compute_build_alias(options) ⇒ Packager::build_config
- #derive_packager(options) ⇒ Packager
- #do_build(executor, tmpdir, packager, options) ⇒ void
- #do_build_with_force_ruby_platform(options) ⇒ void
- #do_print_ruby_cache_key(packager) ⇒ void
-
#initialize(stdout:, stderr:) ⇒ CLI
constructor
A new instance of CLI.
- #pack(args) ⇒ void
- #require_extension ⇒ void
-
#root ⇒ string
Retrieves the root directory of the Ruby project.
- #run(args) ⇒ void
Constructor Details
#initialize(stdout:, stderr:) ⇒ CLI
Returns a new instance of CLI.
6 7 8 9 |
# File 'lib/ruby_wasm/cli.rb', line 6 def initialize(stdout:, stderr:) @stdout = stdout @stderr = stderr end |
Class Method Details
.build_config_aliases(root) ⇒ Hash[string, Packager::build_config]
Retrieves the alias definitions for the Ruby sources.
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/ruby_wasm/cli.rb', line 239 def self.build_config_aliases(root) # @type var aliases: Array[RubyWasm::Packager::build_source] aliases = [ { name: "head", src: { type: "github", repo: "ruby/ruby", rev: "master" }, all_default_exts: RubyWasm::Packager::ALL_DEFAULT_EXTS, wasi_sdk_version: "24.0" }, { name: "4.0", src: { type: "tarball", url: "https://cache.ruby-lang.org/pub/ruby/4.0/ruby-4.0.0.tar.gz" }, all_default_exts: "cgi/escape,continuation,coverage,date,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,json,json/generator,json/parser,objspace,pathname,psych,rbconfig/sizeof,ripper,stringio,strscan,monitor,zlib,openssl", wasi_sdk_version: "24.0" }, { name: "3.4", src: { type: "tarball", url: "https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.1.tar.gz" }, all_default_exts: "cgi/escape,continuation,coverage,date,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,json,json/generator,json/parser,objspace,pathname,psych,rbconfig/sizeof,ripper,stringio,strscan,monitor,zlib,openssl", wasi_sdk_version: "22.0" }, { name: "3.3", src: { type: "tarball", url: "https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.3.tar.gz" }, all_default_exts: "bigdecimal,cgi/escape,continuation,coverage,date,dbm,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,fiber,gdbm,json,json/generator,json/parser,nkf,objspace,pathname,psych,racc/cparse,rbconfig/sizeof,ripper,stringio,strscan,monitor,zlib,openssl", wasi_sdk_version: "22.0" }, { name: "3.2", src: { type: "tarball", url: "https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.4.tar.gz" }, all_default_exts: "bigdecimal,cgi/escape,continuation,coverage,date,dbm,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,fiber,gdbm,json,json/generator,json/parser,nkf,objspace,pathname,psych,racc/cparse,rbconfig/sizeof,ripper,stringio,strscan,monitor,zlib,openssl", wasi_sdk_version: "22.0" } ] # Set the name in the source config. aliases.each { |config| config[:src][:name] = config[:name] } # Apply bundled and user-specified `<root>/patches` directories. aliases.each do |config| patches_dirs = [bundled_patches_path, File.join(root, "patches")] config[:src][:patches] = patches_dirs .flat_map do |patches_dir| Dir[File.join(patches_dir, config[:name], "*.patch")].map do |p| File.(p) end end .uniq end aliases_by_name = aliases.to_h { |config| [config[:name], config] } # Pin the revisions based on build_manifest.json if available. build_manifest = File.join(root, "build_manifest.json") if File.exist?(build_manifest) begin manifest = JSON.parse(File.read(build_manifest)) manifest["ruby_revisions"].each do |name, rev| source = aliases_by_name[name][:src] next unless source[:type] == "github" # @type var source: RubyWasm::Packager::build_source_github source[:rev] = rev end rescue StandardError => e RubyWasm.logger.warn "Failed to load build_manifest.json: #{e}" raise e end end aliases_by_name end |
.bundled_patches_path ⇒ string
Path to the directory containing the bundled patches, which is shipped as part of ruby_wasm gem to backport fixes or try experimental features before landing them to the ruby/ruby repository.
350 351 352 353 354 355 |
# File 'lib/ruby_wasm/cli.rb', line 350 def self.bundled_patches_path dir = __dir__ raise "Unexpected directory structure, no __dir__!??" unless dir lib_source_root = File.join(dir, "..", "..") File.join(lib_source_root, "patches") end |
Instance Method Details
#build(args) ⇒ void
This method returns an undefined value.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ruby_wasm/cli.rb', line 44 def build(args) # @type var options: cli_options = { save_temps: false, remake: false, reconfigure: false, clean: false, ruby_version: "3.3", target_triplet: "wasm32-unknown-wasip1", profile: "full", stdlib: true, without_stdlib_components: [], dest_dir: nil, disable_gems: false, gemfile: nil, patches: [], } OptionParser .new do |opts| opts. = "Usage: rbwasm build [options]" opts.on("-h", "--help", "Prints this help") do @stdout.puts opts exit end opts.on("--save-temps", "Save temporary files") do [:save_temps] = true end opts.on("--ruby-version VERSION", "Ruby version") do |version| [:ruby_version] = version end opts.on("--target TRIPLET", "Target triplet") do |triplet| [:target_triplet] = triplet end opts.on( "--build-profile PROFILE", "Build profile. full or minimal" ) { |profile| [:profile] = profile } opts.on("--remake", "Re-execute make for Ruby") do [:remake] = true end opts.on("--reconfigure", "Re-execute configure for Ruby") do [:reconfigure] = true end opts.on("--clean", "Clean build artifacts") { [:clean] = true } opts.on("-o", "--output FILE", "Output file") do |file| [:output] = file end opts.on("--[no-]stdlib", "Include stdlib") do |stdlib| [:stdlib] = stdlib end opts.on("--without-stdlib COMPONENT", "Exclude stdlib component") do |component| [:without_stdlib_components] << component end opts.on("--disable-gems", "Disable gems") do [:disable_gems] = true end opts.on("--dest-dir PATH", "(Experimental) Destination directory") do |path| [:dest_dir] = path end opts.on("-p", "--patch PATCH", "Apply a patch") do |patch| [:patches] << patch end opts.on("--format FORMAT", "Output format") do |format| [:format] = format end opts.on("--print-ruby-cache-key", "Print Ruby cache key") do [:print_ruby_cache_key] = true end end .parse!(args) __skip__ = if defined?(Bundler) Bundler.settings.temporary(force_ruby_platform: true) do do_build_with_force_ruby_platform() end else do_build_with_force_ruby_platform() end end |
#build_config(options) ⇒ Packager::build_config
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/ruby_wasm/cli.rb', line 178 def build_config() # @type var config: Packager::build_config config = compute_build_alias() config[:target] = [:target_triplet] case [:profile] when "full" config[:default_exts] = config[:all_default_exts] || "" env_additional_exts = ENV["RUBY_WASM_ADDITIONAL_EXTS"] || "" unless env_additional_exts.empty? config[:default_exts] += "," + env_additional_exts end when "minimal" config[:default_exts] = "" else RubyWasm.logger.error "Unknown profile: #{[:profile]} (available: full, minimal)" exit 1 end config[:suffix] = "-#{[:profile]}" config end |
#compute_build_alias(options) ⇒ Packager::build_config
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/ruby_wasm/cli.rb', line 199 def compute_build_alias() src_name = [:ruby_version] aliases = self.class.build_config_aliases(root) config = aliases[src_name] if config.nil? if File.directory?(src_name) # Treat as a local source if the given name is a source directory. RubyWasm.logger.debug "Using local source: #{src_name}" if [:patches].any? RubyWasm.logger.warn "Patches specified through --patch are ignored for local sources" end # FIXME: We should have a way to specify extensions to be included by users. # For now, assume all default extensions available in the head revision are available. # @type var patches: Array[String] patches = [] return( { name: "local", src: { name: "local", type: "local", path: src_name, patches: patches }, all_default_exts: RubyWasm::Packager::ALL_DEFAULT_EXTS, wasi_sdk_version: aliases.fetch("head").fetch(:wasi_sdk_version) } ) end # Otherwise, it's an unknown source. raise( "Unknown Ruby source: #{src_name} (available: #{aliases.keys.join(", ")} or a local directory)" ) end # Apply user-specified patches in addition to bundled patches. config[:src][:patches].concat([:patches]) config end |
#derive_packager(options) ⇒ Packager
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/ruby_wasm/cli.rb', line 357 def derive_packager() __skip__ = definition = nil features = RubyWasm::FeatureSet.derive_from_env # The head ruby & dynamic linking uses "bundle" command to build gems instead of in-process integration. use_in_process_gem_building = !([:ruby_version] == "head" && features.support_dynamic_linking?) __skip__ = if defined?(Bundler) && ![:disable_gems] && use_in_process_gem_building begin # Silence Bundler UI if --print-ruby-cache-key is specified not to bother the JSON output. level = [:print_ruby_cache_key] ? :silent : Bundler.ui.level old_level = Bundler.ui.level Bundler.ui.level = level definition = Bundler.definition ensure Bundler.ui.level = old_level end end RubyWasm.logger.info "Using Gemfile: #{definition.gemfiles.map(&:to_s).join(", ")}" if definition RubyWasm::Packager.new( root, build_config(), definition, features: features, ) end |
#do_build(executor, tmpdir, packager, options) ⇒ void
This method returns an undefined value.
396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/ruby_wasm/cli.rb', line 396 def do_build(executor, tmpdir, packager, ) self.require_extension wasm_bytes = packager.package(executor, tmpdir, ) RubyWasm.logger.info "Size: #{SizeFormatter.format(wasm_bytes.size)}" case [:output] when "-" @stdout.write wasm_bytes else File.binwrite([:output], wasm_bytes) RubyWasm.logger.debug "Wrote #{[:output]}" end end |
#do_build_with_force_ruby_platform(options) ⇒ void
This method returns an undefined value.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/ruby_wasm/cli.rb', line 139 def do_build_with_force_ruby_platform() verbose = RubyWasm.logger.level == :debug executor = RubyWasm::BuildExecutor.new(verbose: verbose) packager = self.derive_packager() if [:print_ruby_cache_key] self.do_print_ruby_cache_key(packager) exit end unless [:output] @stderr.puts "Output file is not specified" exit 1 end require "tmpdir" if dest_dir = [:dest_dir] self.do_build(executor, dest_dir, packager, ) elsif [:save_temps] tmpdir = Dir.mktmpdir self.do_build(executor, tmpdir, packager, ) @stderr.puts "Temporary files are saved to #{tmpdir}" exit else Dir.mktmpdir do |tmpdir| self.do_build(executor, tmpdir, packager, ) end end end |
#do_print_ruby_cache_key(packager) ⇒ void
This method returns an undefined value.
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/ruby_wasm/cli.rb', line 380 def do_print_ruby_cache_key(packager) ruby_core_build = packager.ruby_core_build require "digest" digest = Digest::SHA256.new # The build system key is used to invalidate the cache when the build system is updated. build_system_key = 1 digest.update(build_system_key.to_s) ruby_core_build.cache_key(digest) hexdigest = digest.hexdigest require "json" @stdout.puts JSON.generate( hexdigest: hexdigest, artifact: ruby_core_build.artifact ) end |
#pack(args) ⇒ void
This method returns an undefined value.
171 172 173 174 |
# File 'lib/ruby_wasm/cli.rb', line 171 def pack(args) self.require_extension RubyWasmExt::WasiVfs.run_cli([$0, "pack", *args]) end |
#require_extension ⇒ void
This method returns an undefined value.
409 410 411 412 413 414 415 416 417 |
# File 'lib/ruby_wasm/cli.rb', line 409 def require_extension # Tries to require the extension for the given Ruby version first begin RUBY_VERSION =~ /(\d+\.\d+)/ require_relative "#{Regexp.last_match(1)}/ruby_wasm.so" rescue LoadError require_relative "ruby_wasm.so" end end |
#root ⇒ string
Retrieves the root directory of the Ruby project.
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/ruby_wasm/cli.rb', line 331 def root __skip__ = @root ||= begin if explicit = ENV["RUBY_WASM_ROOT"] File.(explicit) elsif defined?(Bundler) Bundler.root else Dir.pwd end rescue Bundler::GemfileNotFound Dir.pwd end end |
#run(args) ⇒ void
This method returns an undefined value.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/ruby_wasm/cli.rb', line 11 def run(args) available_commands = %w[build pack] parser = OptionParser.new do |opts| opts. = <<~USAGE Usage: rbwasm [options...] [command] Available commands: #{available_commands.join(", ")} USAGE opts.version = RubyWasm::VERSION opts.on("-h", "--help", "Prints this help") do @stderr.puts opts exit end opts.on("--log-level LEVEL", "Log level") do |level| RubyWasm.log_level = level.to_sym end end parser.order!(args) command = args.shift case command when "build" build(args) when "pack" pack(args) else @stderr.puts "Unknown command: #{command}" @stderr.puts parser exit end end |