Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Hbc::SystemCommand. #4488

Merged
merged 7 commits into from
Jul 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions Library/Homebrew/cask/lib/hbc/artifact/artifact.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "hbc/artifact/moved"

require "hbc/utils/hash_validator"
require "extend/hash_validator"
using HashValidator

module Hbc
module Artifact
Expand All @@ -20,7 +21,7 @@ def self.from_args(cask, *args)
raise CaskInvalidError.new(cask.token, "target required for #{english_name} '#{source_string}'")
end

target_hash.extend(HashValidator).assert_valid_keys(:target)
target_hash.assert_valid_keys!(:target)

new(cask, source_string, **target_hash)
end
Expand Down
5 changes: 4 additions & 1 deletion Library/Homebrew/cask/lib/hbc/artifact/installer.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
require "hbc/artifact/abstract_artifact"

require "extend/hash_validator"
using HashValidator

module Hbc
module Artifact
class Installer < AbstractArtifact
Expand Down Expand Up @@ -60,7 +63,7 @@ def self.from_args(cask, **args)
raise CaskInvalidError.new(cask, "invalid 'installer' stanza: Only one of #{VALID_KEYS.inspect} is permitted.")
end

args.extend(HashValidator).assert_valid_keys(*VALID_KEYS)
args.assert_valid_keys!(*VALID_KEYS)
new(cask, **args)
end

Expand Down
11 changes: 5 additions & 6 deletions Library/Homebrew/cask/lib/hbc/artifact/pkg.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
require "hbc/artifact/abstract_artifact"
require "vendor/plist/plist"

require "hbc/utils/hash_validator"
require "hbc/artifact/abstract_artifact"

require "vendor/plist/plist"
require "extend/hash_validator"
using HashValidator

module Hbc
module Artifact
class Pkg < AbstractArtifact
attr_reader :pkg_relative_path

def self.from_args(cask, path, **stanza_options)
stanza_options.extend(HashValidator).assert_valid_keys(
:allow_untrusted, :choices
)
stanza_options.assert_valid_keys!(:allow_untrusted, :choices)
new(cask, path, **stanza_options)
end

Expand Down
5 changes: 3 additions & 2 deletions Library/Homebrew/cask/lib/hbc/artifact/relocated.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "hbc/artifact/abstract_artifact"

require "hbc/utils/hash_validator"
require "extend/hash_validator"
using HashValidator

module Hbc
module Artifact
Expand All @@ -10,7 +11,7 @@ def self.from_args(cask, *args)

if target_hash
raise CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.extend(HashValidator).assert_valid_keys(:target)
target_hash.assert_valid_keys!(:target)
end

target_hash ||= {}
Expand Down
25 changes: 0 additions & 25 deletions Library/Homebrew/cask/lib/hbc/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,6 @@ def to_s
end
end

class CaskCommandFailedError < CaskError
def initialize(cmd, stdout, stderr, status)
@cmd = cmd
@stdout = stdout
@stderr = stderr
@status = status
end

def to_s
s = "Command failed to execute!\n"
s.concat("\n")
s.concat("==> Failed command:\n")
s.concat(@cmd.join(" ")).concat("\n")
s.concat("\n")
s.concat("==> Standard Output of failed command:\n")
s.concat(@stdout).concat("\n")
s.concat("\n")
s.concat("==> Standard Error of failed command:\n")
s.concat(@stderr).concat("\n")
s.concat("\n")
s.concat("==> Exit status of failed command:\n")
s.concat(@status.inspect).concat("\n")
end
end

class CaskX11DependencyError < AbstractCaskErrorWithToken
def to_s
<<~EOS
Expand Down
17 changes: 11 additions & 6 deletions Library/Homebrew/cask/lib/hbc/system_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
require "shellwords"

require "extend/io"

require "hbc/utils/hash_validator"
require "extend/hash_validator"
using HashValidator

module Hbc
class SystemCommand
Expand All @@ -19,6 +19,7 @@ def self.run!(command, **options)
end

def run!
@merged_output = []
@processed_output = { stdout: "", stderr: "" }
odebug command.shelljoin

Expand All @@ -27,9 +28,11 @@ def run!
when :stdout
puts line.chomp if print_stdout?
processed_output[:stdout] << line
@merged_output << [:stdout, line]
when :stderr
$stderr.puts Formatter.error(line.chomp) if print_stderr?
processed_output[:stderr] << line
@merged_output << [:stderr, line]
end
end

Expand All @@ -41,11 +44,11 @@ def initialize(executable, args: [], sudo: false, input: [], print_stdout: false
@executable = executable
@args = args
@sudo = sudo
@input = input
@input = [*input]
@print_stdout = print_stdout
@print_stderr = print_stderr
@must_succeed = must_succeed
options.extend(HashValidator).assert_valid_keys(:chdir)
options.assert_valid_keys!(:chdir)
@options = options
@env = env

Expand Down Expand Up @@ -84,7 +87,9 @@ def sudo_prefix

def assert_success
return if processed_status&.success?
raise CaskCommandFailedError.new(command, processed_output[:stdout], processed_output[:stderr], processed_status)
raise ErrorDuringExecution.new(command,
status: processed_status,
output: @merged_output)
end

def expanded_args
Expand Down Expand Up @@ -113,7 +118,7 @@ def each_output_line(&b)
end

def write_input_to(raw_stdin)
[*input].each(&raw_stdin.method(:print))
input.each(&raw_stdin.method(:write))
end

def each_line_from(sources)
Expand Down
8 changes: 0 additions & 8 deletions Library/Homebrew/cask/lib/hbc/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@

BUG_REPORTS_URL = "https://github.com/Homebrew/homebrew-cask#reporting-bugs".freeze

# global methods

def odebug(title, *sput)
return unless ARGV.debug?
puts Formatter.headline(title, color: :magenta)
puts sput unless sput.empty?
end

module Hbc
module Utils
def self.gain_permissions_remove(path, command: SystemCommand)
Expand Down
7 changes: 0 additions & 7 deletions Library/Homebrew/cask/lib/hbc/utils/hash_validator.rb

This file was deleted.

3 changes: 2 additions & 1 deletion Library/Homebrew/download_strategy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def clear_cache

def safe_system(*args)
if @shutup
quiet_system(*args) || raise(ErrorDuringExecution.new(args.shift, args))
return if quiet_system(*args)
raise(ErrorDuringExecution.new(args, status: $CHILD_STATUS))
else
super(*args)
end
Expand Down
23 changes: 20 additions & 3 deletions Library/Homebrew/exceptions.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "shellwords"

class UsageError < RuntimeError
attr_reader :reason

Expand Down Expand Up @@ -524,9 +526,24 @@ def initialize(cause)

# raised by safe_system in utils.rb
class ErrorDuringExecution < RuntimeError
def initialize(cmd, args = [])
args = args.map { |a| a.to_s.gsub " ", "\\ " }.join(" ")
super "Failure while executing: #{cmd} #{args}"
def initialize(cmd, status:, output: nil)
s = "Failure while executing; `#{cmd.shelljoin.gsub(/\\=/, "=")}` exited with #{status.exitstatus}."

unless [*output].empty?
format_output_line = lambda do |type, line|
if type == :stderr
Formatter.error(line)
else
line
end
end

s << " Here's the output:\n"
s << output.map(&format_output_line).join
s << "\n" unless s.end_with?("\n")
end

super s
end
end

Expand Down
9 changes: 9 additions & 0 deletions Library/Homebrew/extend/hash_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module HashValidator
refine Hash do
def assert_valid_keys!(*valid_keys)
unknown_keys = keys - valid_keys
return if unknown_keys.empty?
raise ArgumentError, "invalid keys: #{unknown_keys.map(&:inspect).join(", ")}"
end
end
end
4 changes: 3 additions & 1 deletion Library/Homebrew/extend/os/linux/keg_relocate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def change_rpath(file, old_prefix, new_prefix)
# patchelf requires that the ELF file have a .dynstr section.
# Skip ELF files that do not have a .dynstr section.
return if ["cannot find section .dynstr", "strange: no string table"].include?(old_rpath)
raise ErrorDuringExecution, "#{cmd_rpath}\n#{old_rpath}" unless $CHILD_STATUS.success?
unless $CHILD_STATUS.success?
raise ErrorDuringExecution.new(cmd_rpath, status: $CHILD_STATUS, output: [:stdout, old_rpath])
end

rpath = old_rpath
.split(":")
Expand Down
9 changes: 3 additions & 6 deletions Library/Homebrew/os/linux/elf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,18 @@ def needed_libraries_using_patchelf(path)
patchelf = DevelopmentTools.locate "patchelf"
if path.dylib?
command = [patchelf, "--print-soname", path.expand_path.to_s]
soname = Utils.popen_read(*command).chomp
raise ErrorDuringExecution, command unless $CHILD_STATUS.success?
soname = Utils.safe_popen_read(*command).chomp
end
command = [patchelf, "--print-needed", path.expand_path.to_s]
needed = Utils.popen_read(*command).split("\n")
raise ErrorDuringExecution, command unless $CHILD_STATUS.success?
needed = Utils.safe_popen_read(*command).split("\n")
[soname, needed]
end

def needed_libraries_using_readelf(path)
soname = nil
needed = []
command = ["readelf", "-d", path.expand_path.to_s]
lines = Utils.popen_read(*command).split("\n")
raise ErrorDuringExecution, command unless $CHILD_STATUS.success?
lines = Utils.safe_popen_read(*command).split("\n")
lines.each do |s|
filename = s[/\[(.*)\]/, 1]
next if filename.nil?
Expand Down
3 changes: 1 addition & 2 deletions Library/Homebrew/patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ def contents; end
def apply
data = contents.gsub("HOMEBREW_PREFIX", HOMEBREW_PREFIX)
args = %W[-g 0 -f -#{strip}]
Utils.popen_write("patch", *args) { |p| p.write(data) }
raise ErrorDuringExecution.new("patch", args) unless $CHILD_STATUS.success?
Utils.safe_popen_write("patch", *args) { |p| p.write(data) }
end

def inspect
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/test/cask/system_command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
it "throws an error" do
expect {
described_class.run!(command)
}.to raise_error(Hbc::CaskCommandFailedError)
}.to raise_error(ErrorDuringExecution)
end
end

Expand Down
63 changes: 63 additions & 0 deletions Library/Homebrew/test/error_during_execution_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
describe ErrorDuringExecution do
subject(:error) { described_class.new(command, status: status, output: output) }
let(:command) { ["false"] }
let(:status) { instance_double(Process::Status, exitstatus: exitstatus) }
let(:exitstatus) { 1 }
let(:output) { nil }

describe "#initialize" do
it "fails when only given a command" do
expect {
described_class.new(command)
}.to raise_error(ArgumentError)
end

it "fails when only given a status" do
expect {
described_class.new(status: status)
}.to raise_error(ArgumentError)
end

it "does not raise an error when given both a command and a status" do
expect {
described_class.new(command, status: status)
}.not_to raise_error
end
end

describe "#to_s" do
context "when only given a command and a status" do
its(:to_s) { is_expected.to eq "Failure while executing; `false` exited with 1." }
end

context "when additionally given the output" do
let(:output) {
[
[:stdout, "This still worked.\n"],
[:stderr, "Here something went wrong.\n"],
]
}

before do
allow($stdout).to receive(:tty?).and_return(true)
end

its(:to_s) {
is_expected.to eq <<~EOS
Failure while executing; `false` exited with 1. Here's the output:
This still worked.
#{Formatter.error("Here something went wrong.\n")}
EOS
}
end

context "when command arguments contain special characters" do
let(:command) { ["env", "PATH=/bin", "cat", "with spaces"] }

its(:to_s) {
is_expected
.to eq 'Failure while executing; `env PATH=/bin cat with\ spaces` exited with 1.'
}
end
end
end
5 changes: 3 additions & 2 deletions Library/Homebrew/test/exceptions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ class Baz < Formula; end
end

describe ErrorDuringExecution do
subject { described_class.new("badprg", %w[arg1 arg2]) }
subject { described_class.new(["badprg", "arg1", "arg2"], status: status) }
let(:status) { instance_double(Process::Status, exitstatus: 17) }

its(:to_s) { is_expected.to eq("Failure while executing: badprg arg1 arg2") }
its(:to_s) { is_expected.to eq("Failure while executing; `badprg arg1 arg2` exited with 17.") }
end

describe ChecksumMismatchError do
Expand Down