From fb6c51da074a5148af34c70d790f499b81898307 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Sun, 31 Aug 2025 11:09:44 -0700 Subject: [PATCH] Enable strict typing in SystemCommand --- .../cask/artifact/abstract_uninstall.rb | 8 +- Library/Homebrew/cask/audit.rb | 3 +- Library/Homebrew/cask/caskroom.rb | 2 +- .../Homebrew/extend/os/linux/system_config.rb | 2 +- Library/Homebrew/extend/pathname.rb | 12 +- Library/Homebrew/livecheck/strategy/git.rb | 4 +- Library/Homebrew/readall.rb | 2 +- Library/Homebrew/services/system/systemctl.rb | 2 +- Library/Homebrew/system_command.rb | 196 +++++++++++++----- Library/Homebrew/system_config.rb | 2 +- Library/Homebrew/test/utils/user_spec.rb | 3 +- Library/Homebrew/unpack_strategy/dmg.rb | 6 +- Library/Homebrew/unpack_strategy/tar.rb | 4 +- Library/Homebrew/utils/curl.rb | 2 +- Library/Homebrew/utils/git.rb | 2 +- Library/Homebrew/utils/github/api.rb | 8 +- Library/Homebrew/utils/svn.rb | 7 +- Library/Homebrew/utils/tar.rb | 3 +- Library/Homebrew/utils/user.rb | 8 +- 19 files changed, 188 insertions(+), 88 deletions(-) diff --git a/Library/Homebrew/cask/artifact/abstract_uninstall.rb b/Library/Homebrew/cask/artifact/abstract_uninstall.rb index 62a78986eb..9d3fee476b 100644 --- a/Library/Homebrew/cask/artifact/abstract_uninstall.rb +++ b/Library/Homebrew/cask/artifact/abstract_uninstall.rb @@ -172,7 +172,7 @@ module Cask .stdout.lines.drop(1) # skip stdout column headers .filter_map do |line| pid, _state, id = line.chomp.split(/\s+/) - id if pid.to_i.nonzero? && id.match?(regex) + id if pid.to_i.nonzero? && T.must(id).match?(regex) end end @@ -460,9 +460,9 @@ module Cask def trash_paths(*paths, command: nil, **_) return if paths.empty? - stdout, = system_command HOMEBREW_LIBRARY_PATH/"cask/utils/trash.swift", - args: paths, - print_stderr: Homebrew::EnvConfig.developer? + stdout = system_command(HOMEBREW_LIBRARY_PATH/"cask/utils/trash.swift", + args: paths, + print_stderr: Homebrew::EnvConfig.developer?).stdout trashed, _, untrashable = stdout.partition("\n") trashed = trashed.split(":") diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index e66e14c3a5..bff8f32899 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -543,6 +543,7 @@ module Cask print_stderr: false) else add_error "Unknown artifact type: #{artifact.class}", location: url.location + next end next false if result.success? @@ -695,7 +696,7 @@ module Cask add_error "No binaries in App: #{artifact.source}", location: url.location if files.empty? main_binary = get_plist_main_binary(path) - main_binary ||= files.first + main_binary ||= files.fetch(0) system_command("lipo", args: ["-archs", main_binary], print_stderr: false) when Artifact::Binary diff --git a/Library/Homebrew/cask/caskroom.rb b/Library/Homebrew/cask/caskroom.rb index c0e88584fd..f04f27e3dd 100644 --- a/Library/Homebrew/cask/caskroom.rb +++ b/Library/Homebrew/cask/caskroom.rb @@ -49,7 +49,7 @@ module Cask SystemCommand.run("/bin/mkdir", args: ["-p", path], sudo:) SystemCommand.run("/bin/chmod", args: ["g+rwx", path], sudo:) - SystemCommand.run("/usr/sbin/chown", args: [User.current, path], sudo:) + SystemCommand.run("/usr/sbin/chown", args: [User.current.to_s, path], sudo:) SystemCommand.run("/usr/bin/chgrp", args: ["admin", path], sudo:) end diff --git a/Library/Homebrew/extend/os/linux/system_config.rb b/Library/Homebrew/extend/os/linux/system_config.rb index a9aa5213d4..6a967b3a2d 100644 --- a/Library/Homebrew/extend/os/linux/system_config.rb +++ b/Library/Homebrew/extend/os/linux/system_config.rb @@ -36,7 +36,7 @@ module OS end def host_ruby_version - out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false) + out, _, status = system_command(HOST_RUBY_PATH, args: ["-e", "puts RUBY_VERSION"], print_stderr: false).to_a return "N/A" unless status.success? out diff --git a/Library/Homebrew/extend/pathname.rb b/Library/Homebrew/extend/pathname.rb index 40b6356d7f..710270978a 100644 --- a/Library/Homebrew/extend/pathname.rb +++ b/Library/Homebrew/extend/pathname.rb @@ -422,11 +422,13 @@ class Pathname sig { returns(T::Array[String]) } def zipinfo - @zipinfo ||= T.let(nil, T.nilable(String)) - @zipinfo ||= system_command("zipinfo", args: ["-1", self], print_stderr: false) - .stdout - .encode(Encoding::UTF_8, invalid: :replace) - .split("\n") + @zipinfo ||= T.let( + system_command("zipinfo", args: ["-1", self], print_stderr: false) + .stdout + .encode(Encoding::UTF_8, invalid: :replace) + .split("\n"), + T.nilable(T::Array[String]), + ) end private diff --git a/Library/Homebrew/livecheck/strategy/git.rb b/Library/Homebrew/livecheck/strategy/git.rb index 5b4c07205e..0b303f98e1 100644 --- a/Library/Homebrew/livecheck/strategy/git.rb +++ b/Library/Homebrew/livecheck/strategy/git.rb @@ -129,9 +129,9 @@ module Homebrew print_stderr: false, debug: false, verbose: false, - ) + ).to_a - tags_data = { tags: [] } + tags_data = { tags: T.let([], T::Array[String]) } tags_data[:messages] = stderr.split("\n") if stderr.present? return tags_data if stdout.blank? diff --git a/Library/Homebrew/readall.rb b/Library/Homebrew/readall.rb index 38fe38102b..1c60c3bd99 100644 --- a/Library/Homebrew/readall.rb +++ b/Library/Homebrew/readall.rb @@ -125,7 +125,7 @@ module Readall sig { params(filename: Pathname).returns(T::Boolean) } private_class_method def self.syntax_errors_or_warnings?(filename) # Retrieve messages about syntax errors/warnings printed to `$stderr`. - _, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false) + _, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false).to_a # Ignore unnecessary warning about named capture conflicts. # See https://bugs.ruby-lang.org/issues/12359. diff --git a/Library/Homebrew/services/system/systemctl.rb b/Library/Homebrew/services/system/systemctl.rb index 9793314c75..58bc09b987 100644 --- a/Library/Homebrew/services/system/systemctl.rb +++ b/Library/Homebrew/services/system/systemctl.rb @@ -34,7 +34,7 @@ module Homebrew private_class_method def self._run(*args, mode:) require "system_command" - result = SystemCommand.run(executable, + result = SystemCommand.run(T.must(executable), args: [scope, *args.map(&:to_s)], print_stdout: mode == :default, print_stderr: mode == :default, diff --git a/Library/Homebrew/system_command.rb b/Library/Homebrew/system_command.rb index 6d7d221ec0..b819da1ba2 100644 --- a/Library/Homebrew/system_command.rb +++ b/Library/Homebrew/system_command.rb @@ -1,4 +1,4 @@ -# typed: true # rubocop:todo Sorbet/StrictSigil +# typed: strict # frozen_string_literal: true require "plist" @@ -21,33 +21,119 @@ class SystemCommand # Run a fallible system command. # # @api internal - def system_command(executable, **options) - SystemCommand.run(executable, **options) + sig { + params( + executable: T.any(String, Pathname), + args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], + sudo: T::Boolean, + sudo_as_root: T::Boolean, + env: T::Hash[String, String], + input: T.any(String, T::Array[String]), + must_succeed: T::Boolean, + print_stdout: T.any(T::Boolean, Symbol), + print_stderr: T.any(T::Boolean, Symbol), + debug: T.nilable(T::Boolean), + verbose: T.nilable(T::Boolean), + secrets: T.any(String, T::Array[String]), + chdir: T.any(String, Pathname), + reset_uid: T::Boolean, + timeout: T.nilable(T.any(Integer, Float)), + ).returns(SystemCommand::Result) + } + def system_command(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], + must_succeed: false, print_stdout: false, print_stderr: true, debug: nil, verbose: nil, + secrets: [], chdir: T.unsafe(nil), reset_uid: false, timeout: nil) + SystemCommand.run(executable, args:, sudo:, sudo_as_root:, env:, input:, must_succeed:, print_stdout:, + print_stderr:, debug:, verbose:, secrets:, chdir:, reset_uid:, timeout:) end # Run an infallible system command. # # @api internal - def system_command!(command, **options) - SystemCommand.run!(command, **options) + sig { + params( + executable: T.any(String, Pathname), + args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], + sudo: T::Boolean, + sudo_as_root: T::Boolean, + env: T::Hash[String, String], + input: T.any(String, T::Array[String]), + print_stdout: T.any(T::Boolean, Symbol), + print_stderr: T.any(T::Boolean, Symbol), + debug: T.nilable(T::Boolean), + verbose: T.nilable(T::Boolean), + secrets: T.any(String, T::Array[String]), + chdir: T.any(String, Pathname), + reset_uid: T::Boolean, + timeout: T.nilable(T.any(Integer, Float)), + ).returns(SystemCommand::Result) + } + def system_command!(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], + print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], + chdir: T.unsafe(nil), reset_uid: false, timeout: nil) + SystemCommand.run!(executable, args:, sudo:, sudo_as_root:, env:, input:, print_stdout:, + print_stderr:, debug:, verbose:, secrets:, chdir:, reset_uid:, timeout:) end end include Context - def self.run(executable, **options) - new(executable, **options).run! + sig { + params( + executable: T.any(String, Pathname), + args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], + sudo: T::Boolean, + sudo_as_root: T::Boolean, + env: T::Hash[String, String], + input: T.any(String, T::Array[String]), + must_succeed: T::Boolean, + print_stdout: T.any(T::Boolean, Symbol), + print_stderr: T.any(T::Boolean, Symbol), + debug: T.nilable(T::Boolean), + verbose: T.nilable(T::Boolean), + secrets: T.any(String, T::Array[String]), + chdir: T.any(NilClass, String, Pathname), + reset_uid: T::Boolean, + timeout: T.nilable(T.any(Integer, Float)), + ).returns(SystemCommand::Result) + } + def self.run(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], must_succeed: false, + print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil, + reset_uid: false, timeout: nil) + new(executable, args:, sudo:, sudo_as_root:, env:, input:, must_succeed:, print_stdout:, print_stderr:, debug:, + verbose:, secrets:, chdir:, reset_uid:, timeout:).run! end - def self.run!(command, **options) - run(command, **options, must_succeed: true) + sig { + params( + executable: T.any(String, Pathname), + args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], + sudo: T::Boolean, + sudo_as_root: T::Boolean, + env: T::Hash[String, String], + input: T.any(String, T::Array[String]), + print_stdout: T.any(T::Boolean, Symbol), + print_stderr: T.any(T::Boolean, Symbol), + debug: T.nilable(T::Boolean), + verbose: T.nilable(T::Boolean), + secrets: T.any(String, T::Array[String]), + chdir: T.any(NilClass, String, Pathname), + reset_uid: T::Boolean, + timeout: T.nilable(T.any(Integer, Float)), + ).returns(SystemCommand::Result) + } + def self.run!(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], print_stdout: false, + print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil, reset_uid: false, timeout: nil) + run(executable, args:, sudo:, sudo_as_root:, env:, input:, must_succeed: true, print_stdout:, print_stderr:, + debug:, verbose:, secrets:, chdir:, reset_uid:, timeout:) end sig { returns(SystemCommand::Result) } def run! $stderr.puts redact_secrets(command.shelljoin.gsub('\=', "="), @secrets) if verbose? && debug? - @output = [] + @output = T.let([], T.nilable(T::Array[[Symbol, String]])) + @output = T.must(@output) each_output_line do |type, line| case type @@ -70,7 +156,7 @@ class SystemCommand end end - result = Result.new(command, @output, @status, secrets: @secrets) + result = Result.new(command, @output, T.must(@status), secrets: @secrets) result.assert_success! if must_succeed? result end @@ -78,7 +164,7 @@ class SystemCommand sig { params( executable: T.any(String, Pathname), - args: T::Array[T.any(String, Integer, Float, URI::Generic)], + args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], sudo: T::Boolean, sudo_as_root: T::Boolean, env: T::Hash[String, String], @@ -89,28 +175,14 @@ class SystemCommand debug: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), secrets: T.any(String, T::Array[String]), - chdir: T.any(String, Pathname), + chdir: T.any(NilClass, String, Pathname), reset_uid: T::Boolean, timeout: T.nilable(T.any(Integer, Float)), ).void } - def initialize( - executable, - args: [], - sudo: false, - sudo_as_root: false, - env: {}, - input: [], - must_succeed: false, - print_stdout: false, - print_stderr: true, - debug: nil, - verbose: T.unsafe(nil), - secrets: [], - chdir: T.unsafe(nil), - reset_uid: false, - timeout: nil - ) + def initialize(executable, args: [], sudo: false, sudo_as_root: false, env: {}, input: [], must_succeed: false, + print_stdout: false, print_stderr: true, debug: nil, verbose: nil, secrets: [], chdir: nil, + reset_uid: false, timeout: nil) require "extend/ENV" @executable = executable @args = args @@ -132,13 +204,13 @@ class SystemCommand raise ArgumentError, "Invalid variable name: #{name}" end @env = env - @input = Array(input) + @input = T.let(Array(input), T::Array[String]) @must_succeed = must_succeed @print_stdout = print_stdout @print_stderr = print_stderr @debug = debug @verbose = verbose - @secrets = (Array(secrets) + ENV.sensitive_environment.values).uniq + @secrets = T.let((Array(secrets) + ENV.sensitive_environment.values).uniq, T::Array[String]) @chdir = chdir @reset_uid = reset_uid @timeout = timeout @@ -151,7 +223,20 @@ class SystemCommand private - attr_reader :executable, :args, :input, :chdir, :env + sig { returns(T.any(Pathname, String)) } + attr_reader :executable + + sig { returns(T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)]) } + attr_reader :args + + sig { returns(T::Array[String]) } + attr_reader :input + + sig { returns(T.any(NilClass, String, Pathname)) } + attr_reader :chdir + + sig { returns(T::Hash[String, String]) } + attr_reader :env sig { returns(T::Boolean) } def must_succeed? = @must_succeed @@ -227,15 +312,13 @@ class SystemCommand sig { returns(T::Array[String]) } def expanded_args - @expanded_args ||= args.map do |arg| - if arg.respond_to?(:to_path) + @expanded_args ||= T.let(args.map do |arg| + if arg.is_a?(Pathname) File.absolute_path(arg) - elsif arg.is_a?(Integer) || arg.is_a?(Float) || arg.is_a?(URI::Generic) - arg.to_s else - arg.to_str + arg.to_s end - end + end, T.nilable(T::Array[String])) end class ProcessTerminatedInterrupt < StandardError; end @@ -275,7 +358,7 @@ class SystemCommand end_time = Time.now + @timeout if @timeout raise Timeout::Error if raw_wait_thr.join(Utils::Timer.remaining(end_time)).nil? - @status = raw_wait_thr.value + @status = T.let(raw_wait_thr.value, T.nilable(Process::Status)) rescue Interrupt Process.kill("INT", raw_wait_thr.pid) if raw_wait_thr && !sudo? raise Interrupt @@ -383,7 +466,14 @@ class SystemCommand include Context include Utils::Output::Mixin - attr_accessor :command, :status, :exit_status + sig { returns(T::Array[String]) } + attr_accessor :command + + sig { returns(Process::Status) } + attr_accessor :status + + sig { returns(T.nilable(Integer)) } + attr_accessor :exit_status sig { params( @@ -397,7 +487,7 @@ class SystemCommand @command = command @output = output @status = status - @exit_status = status.exitstatus + @exit_status = T.let(status.exitstatus, T.nilable(Integer)) @secrets = secrets end @@ -410,22 +500,21 @@ class SystemCommand sig { returns(String) } def stdout - @stdout ||= @output.select { |type,| type == :stdout } - .map { |_, line| line } - .join + @stdout ||= T.let(@output.select { |type,| type == :stdout } + .map { |_, line| line } + .join, T.nilable(String)) end sig { returns(String) } def stderr - @stderr ||= @output.select { |type,| type == :stderr } - .map { |_, line| line } - .join + @stderr ||= T.let(@output.select { |type,| type == :stderr } + .map { |_, line| line } + .join, T.nilable(String)) end sig { returns(String) } def merged_output - @merged_output ||= @output.map { |_, line| line } - .join + @merged_output ||= T.let(@output.map { |_, line| line }.join, T.nilable(String)) end sig { returns(T::Boolean) } @@ -439,10 +528,11 @@ class SystemCommand def to_ary [stdout, stderr, status] end + alias to_a to_ary - sig { returns(T.nilable(T.any(Array, Hash))) } + sig { returns(T.untyped) } def plist - @plist ||= begin + @plist ||= T.let(begin output = stdout output = output.sub(/\A(.*?)(\s*<\?\s*xml)/m) do @@ -456,7 +546,7 @@ class SystemCommand end Plist.parse_xml(output, marshal: false) - end + end, T.untyped) end sig { params(garbage: String).void } diff --git a/Library/Homebrew/system_config.rb b/Library/Homebrew/system_config.rb index 3a1a53cb44..922a537547 100644 --- a/Library/Homebrew/system_config.rb +++ b/Library/Homebrew/system_config.rb @@ -102,7 +102,7 @@ module SystemConfig sig { returns(String) } def describe_curl - out, = system_command(Utils::Curl.curl_executable, args: ["--version"], verbose: false) + out = system_command(Utils::Curl.curl_executable, args: ["--version"], verbose: false).stdout match_data = /^curl (?[\d.]+)/.match(out) if match_data diff --git a/Library/Homebrew/test/utils/user_spec.rb b/Library/Homebrew/test/utils/user_spec.rb index 111b8d34ec..02a0400e07 100644 --- a/Library/Homebrew/test/utils/user_spec.rb +++ b/Library/Homebrew/test/utils/user_spec.rb @@ -11,7 +11,8 @@ RSpec.describe User do before do allow(SystemCommand).to receive(:run) .with("who", any_args) - .and_return([who_output, "", instance_double(Process::Status, success?: true)]) + .and_return(instance_double(SystemCommand::Result, + to_a: [who_output, "", instance_double(Process::Status, success?: true)])) end context "when the current user is in a console session" do diff --git a/Library/Homebrew/unpack_strategy/dmg.rb b/Library/Homebrew/unpack_strategy/dmg.rb index ca8ca097ed..328a25d373 100644 --- a/Library/Homebrew/unpack_strategy/dmg.rb +++ b/Library/Homebrew/unpack_strategy/dmg.rb @@ -155,7 +155,7 @@ module UnpackStrategy filelist.close system_command! "mkbom", - args: ["-s", "-i", filelist.path, "--", bomfile.path], + args: ["-s", "-i", T.must(filelist.path), "--", T.must(bomfile.path)], verbose: end @@ -179,8 +179,8 @@ module UnpackStrategy sig { override.params(path: Pathname).returns(T::Boolean) } def self.can_extract?(path) - stdout, _, status = system_command("hdiutil", args: ["imageinfo", "-format", path], print_stderr: false) - status.success? && !stdout.empty? + stdout, _, status = system_command("hdiutil", args: ["imageinfo", "-format", path], print_stderr: false).to_a + (status.success? && !stdout.empty?) || false end private diff --git a/Library/Homebrew/unpack_strategy/tar.rb b/Library/Homebrew/unpack_strategy/tar.rb index 65d5750847..0adfccb5ea 100644 --- a/Library/Homebrew/unpack_strategy/tar.rb +++ b/Library/Homebrew/unpack_strategy/tar.rb @@ -29,8 +29,8 @@ module UnpackStrategy return false unless [Bzip2, Gzip, Lzip, Xz, Zstd].any? { |s| s.can_extract?(path) } # Check if `tar` can list the contents, then it can also extract it. - stdout, _, status = system_command("tar", args: ["--list", "--file", path], print_stderr: false) - status.success? && !stdout.empty? + stdout, _, status = system_command("tar", args: ["--list", "--file", path], print_stderr: false).to_a + (status.success? && !stdout.empty?) || false end private diff --git a/Library/Homebrew/utils/curl.rb b/Library/Homebrew/utils/curl.rb index b1f7643c6b..259fb59d1d 100644 --- a/Library/Homebrew/utils/curl.rb +++ b/Library/Homebrew/utils/curl.rb @@ -183,7 +183,7 @@ module Utils return result if result.success? || args.include?("--http1.1") - raise Timeout::Error, result.stderr.lines.last.chomp if timeout && result.status.exitstatus == 28 + raise Timeout::Error, result.stderr.lines.fetch(-1).chomp if timeout && result.status.exitstatus == 28 # Error in the HTTP2 framing layer if result.exit_status == 16 diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb index 1a7da5cd32..8669acf504 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -17,7 +17,7 @@ module Utils def self.version return @version if defined?(@version) - stdout, _, status = system_command(git, args: ["--version"], verbose: false, print_stderr: false) + stdout, _, status = system_command(git, args: ["--version"], verbose: false, print_stderr: false).to_a @version = status.success? ? stdout.chomp[/git version (\d+(?:\.\d+)*)/, 1] : nil end diff --git a/Library/Homebrew/utils/github/api.rb b/Library/Homebrew/utils/github/api.rb index 53d4733bba..09bfd58323 100644 --- a/Library/Homebrew/utils/github/api.rb +++ b/Library/Homebrew/utils/github/api.rb @@ -163,10 +163,10 @@ module GitHub "PATH" => PATH.new(HOMEBREW_PREFIX/"opt/gh/bin", ENV.fetch("PATH")), "HOME" => Utils::UID.uid_home, }.compact - gh_out, _, result = system_command "gh", + gh_out, _, result = system_command("gh", args: ["auth", "token", "--hostname", "github.com"], env:, - print_stderr: false + print_stderr: false).to_a return unless result.success? gh_out.chomp.presence @@ -179,11 +179,11 @@ module GitHub def self.keychain_username_password require "utils/uid" Utils::UID.drop_euid do - git_credential_out, _, result = system_command "git", + git_credential_out, _, result = system_command("git", args: ["credential-osxkeychain", "get"], input: ["protocol=https\n", "host=github.com\n"], env: { "HOME" => Utils::UID.uid_home }.compact, - print_stderr: false + print_stderr: false).to_a return unless result.success? git_credential_out.force_encoding("ASCII-8BIT") diff --git a/Library/Homebrew/utils/svn.rb b/Library/Homebrew/utils/svn.rb index 2ea4f93d52..80eb3f3d18 100644 --- a/Library/Homebrew/utils/svn.rb +++ b/Library/Homebrew/utils/svn.rb @@ -20,7 +20,8 @@ module Utils def version return @version if defined?(@version) - stdout, _, status = system_command(HOMEBREW_SHIMS_PATH/"shared/svn", args: ["--version"], print_stderr: false) + stdout, _, status = system_command(HOMEBREW_SHIMS_PATH/"shared/svn", args: ["--version"], + print_stderr: false).to_a @version = T.let(status.success? ? stdout.chomp[/svn, version (\d+(?:\.\d+)*)/, 1] : nil, T.nilable(String)) end @@ -29,8 +30,8 @@ module Utils return true unless available? args = ["ls", url, "--depth", "empty"] - _, stderr, status = system_command("svn", args:, print_stderr: false) - return status.success? unless stderr.include?("certificate verification failed") + _, stderr, status = system_command("svn", args:, print_stderr: false).to_a + return !!status.success? unless stderr.include?("certificate verification failed") # OK to unconditionally trust here because we're just checking if a URL exists. system_command("svn", args: args.concat(invalid_cert_flags), print_stderr: false).success? diff --git a/Library/Homebrew/utils/tar.rb b/Library/Homebrew/utils/tar.rb index 6878e702c5..5a50805bbd 100644 --- a/Library/Homebrew/utils/tar.rb +++ b/Library/Homebrew/utils/tar.rb @@ -34,7 +34,8 @@ module Utils path = Pathname.new(path) return unless TAR_FILE_EXTENSIONS.include? path.extname - stdout, _, status = system_command(executable, args: ["--list", "--file", path], print_stderr: false) + stdout, _, status = system_command(T.must(executable), args: ["--list", "--file", path], + print_stderr: false).to_a odie "#{path} is not a valid tar file!" if !status.success? || stdout.blank? end diff --git a/Library/Homebrew/utils/user.rb b/Library/Homebrew/utils/user.rb index 72339509f6..a29f279a8a 100644 --- a/Library/Homebrew/utils/user.rb +++ b/Library/Homebrew/utils/user.rb @@ -13,12 +13,12 @@ class User < SimpleDelegator # Return whether the user has an active GUI session. sig { returns(T::Boolean) } def gui? - out, _, status = system_command "who" + out, _, status = system_command("who").to_a return false unless status.success? out.lines .map(&:split) - .any? { |user, type,| user == self && type == "console" } + .any? { |user, type,| to_s == user && type == "console" } end # Return the current user. @@ -31,4 +31,8 @@ class User < SimpleDelegator @current = T.let(new(pwuid.name), T.nilable(T.attached_class)) end + + # This explicit delegator exists to make to_s visible to sorbet. + sig { returns(String) } + def to_s = __getobj__.to_s end