diff --git a/Steepfile b/Steepfile index f705d75bc8..98977dcd8d 100644 --- a/Steepfile +++ b/Steepfile @@ -13,6 +13,7 @@ target :lib do library "open-uri" library "uri" library "shellwords" + library "io-console" configure_code_diagnostics(D::Ruby.default) end diff --git a/lib/ruby_wasm/build.rb b/lib/ruby_wasm/build.rb index efe1e30fa6..026929cf87 100644 --- a/lib/ruby_wasm/build.rb +++ b/lib/ruby_wasm/build.rb @@ -10,34 +10,67 @@ def initialize(verbose: false) @github_actions_markup = ENV["ENABLE_GITHUB_ACTIONS_MARKUP"] != nil end - def system(*args, chdir: nil, out: nil, env: nil) + def system(*args, chdir: nil, env: nil) + require "open3" + _print_command(args, env) - if @verbose - out ||= $stdout - else - # Capture stdout by default - out_pipe = IO.pipe - out = out_pipe[1] - end # @type var kwargs: Hash[Symbol, untyped] - kwargs = { exception: true, out: out } + kwargs = {} kwargs[:chdir] = chdir if chdir - begin - if env - Kernel.system(env, *args.to_a.map(&:to_s), **kwargs) + + args = args.to_a.map(&:to_s) + # TODO: Remove __skip__ once we have open3 RBS definitions. + __skip__ = + if @verbose || !$stdout.tty? + kwargs[:exception] = true + if env + Kernel.system(env, *args, **kwargs) + else + Kernel.system(*args, **kwargs) + end else - Kernel.system(*args.to_a.map(&:to_s), **kwargs) + printer = StatusPrinter.new + block = + proc do |stdin, stdout, stderr, wait_thr| + mux = Mutex.new + out = String.new + err = String.new + readers = + [ + [stdout, :stdout, out], + [stderr, :stderr, err] + ].map do |io, name, str| + reader = + Thread.new do + while (line = io.gets) + mux.synchronize do + printer.send(name, line) + str << line + end + end + end + reader.report_on_exception = false + reader + end + + readers.each(&:join) + + [out, err, wait_thr.value] + end + begin + if env + Open3.popen3(env, *args, **kwargs, &block) + else + Open3.popen3(*args, **kwargs, &block) + end + ensure + printer.done + end end - ensure - out.close if out_pipe - end rescue => e - if out_pipe - # Print the output of the failed command - puts out_pipe[0].read - end $stdout.flush + $stderr.puts "Try running with `rake --verbose` for more complete output." raise e end @@ -58,9 +91,7 @@ def begin_section(klass, name, note) def end_section(klass, name) took = Time.now - @start_times[[klass, name]] - if @github_actions_markup - puts "::endgroup::" - end + puts "::endgroup::" if @github_actions_markup puts "\e[1;36m==>\e[0m \e[1m#{klass}(#{name}) -- done in #{took.round(2)}s\e[0m" end @@ -98,4 +129,46 @@ def _print_command(args, env) print args.map { |arg| Shellwords.escape(arg.to_s) }.join(" ") + "\n" end end + + # Human readable status printer for the build. + class StatusPrinter + def initialize + @mutex = Mutex.new + @counter = 0 + @indicators = "|/-\\" + end + + def stdout(message) + require "io/console" + @mutex.synchronize do + $stdout.print "\e[K" + first_line = message.lines(chomp: true).first || "" + + # Make sure we don't line-wrap the output + size = + __skip__ = + IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize + terminal_width = size[1].to_i.nonzero? || 80 + width_limit = terminal_width / 2 - 3 + + if first_line.length > width_limit + first_line = (first_line[0..width_limit - 5] || "") + "..." + end + indicator = @indicators[@counter] || " " + to_print = " " + indicator + " " + first_line + $stdout.print to_print + $stdout.print "\e[1A\n" + @counter += 1 + @counter = 0 if @counter >= @indicators.length + end + end + + def stderr(message) + @mutex.synchronize { $stdout.print message } + end + + def done + @mutex.synchronize { $stdout.print "\e[K" } + end + end end diff --git a/lib/ruby_wasm/build/product/wasi_vfs.rb b/lib/ruby_wasm/build/product/wasi_vfs.rb index 81022d52ee..1d27b0730b 100644 --- a/lib/ruby_wasm/build/product/wasi_vfs.rb +++ b/lib/ruby_wasm/build/product/wasi_vfs.rb @@ -48,7 +48,11 @@ def build(executor) lib_wasi_vfs_url = "https://github.com/kateinoigakukun/wasi-vfs/releases/download/v#{WASI_VFS_VERSION}/libwasi_vfs-wasm32-unknown-unknown.zip" Dir.mktmpdir do |tmpdir| - executor.system "curl", "-L", lib_wasi_vfs_url, "-o", "#{tmpdir}/libwasi_vfs.zip" + executor.system "curl", + "-L", + lib_wasi_vfs_url, + "-o", + "#{tmpdir}/libwasi_vfs.zip" executor.system "unzip", "#{tmpdir}/libwasi_vfs.zip", "-d", tmpdir executor.mkdir_p File.dirname(lib_wasi_vfs_a) executor.mv File.join(tmpdir, "libwasi_vfs.a"), lib_wasi_vfs_a diff --git a/sig/ruby_wasm/build.rbs b/sig/ruby_wasm/build.rbs index 05cf78c5af..396d533135 100644 --- a/sig/ruby_wasm/build.rbs +++ b/sig/ruby_wasm/build.rbs @@ -227,7 +227,7 @@ module RubyWasm @start_times: Hash[[Class, String], Time] def initialize: (?verbose: bool) -> void - def system: (*_ToS args, ?chdir: String?, ?out: Kernel::redirect_fd?, ?env: Hash[String, String]?) -> void + def system: (*_ToS args, ?chdir: String?, ?env: Hash[String, String]?) -> void def rm_rf: (FileUtils::pathlist list) -> void def rm_f: (FileUtils::pathlist list) -> void def cp_r: (FileUtils::pathlist src, path dest) -> void @@ -241,6 +241,17 @@ module RubyWasm private def _print_command: (Array[_ToS] command, Hash[String, String]? env) -> void end + class StatusPrinter + @mutex: Mutex + @counter: Integer + @indicators: String + + def initialize: () -> void + def stdout: (String message) -> void + def stderr: (String message) -> void + def done: () -> void + end + class Downloader def format_size: (Integer size) -> String