2014-01-26 04:32:20 +08:00
|
|
|
require 'open3'
|
2015-12-30 18:20:21 +08:00
|
|
|
require 'shellwords'
|
2014-01-26 04:32:20 +08:00
|
|
|
|
2015-01-01 22:08:06 +08:00
|
|
|
class Hbc::SystemCommand
|
2014-01-26 04:32:20 +08:00
|
|
|
def self.run(executable, options={})
|
|
|
|
command = _process_options(executable, options)
|
2014-06-07 05:01:30 +08:00
|
|
|
odebug "Executing: #{command.utf8_inspect}"
|
2014-09-25 21:44:24 +08:00
|
|
|
processed_stdout = ''
|
|
|
|
processed_stderr = ''
|
|
|
|
|
2015-12-30 18:20:21 +08:00
|
|
|
command[0] = Shellwords.shellescape(command[0]) if command.size == 1
|
|
|
|
|
2014-12-27 01:04:05 +08:00
|
|
|
raw_stdin, raw_stdout, raw_stderr, raw_wait_thr =
|
2014-12-28 08:22:38 +08:00
|
|
|
Open3.popen3(*command.map { |arg| arg.respond_to?(:to_path) ? File.absolute_path(arg) : String(arg) })
|
2014-07-27 05:29:56 +08:00
|
|
|
|
|
|
|
if options[:input]
|
2014-10-10 20:59:31 +08:00
|
|
|
Array(options[:input]).each { |line| raw_stdin.puts line }
|
2014-07-27 05:29:56 +08:00
|
|
|
end
|
2014-09-25 21:44:24 +08:00
|
|
|
raw_stdin.close_write
|
|
|
|
|
|
|
|
while line = raw_stdout.gets
|
|
|
|
processed_stdout << line
|
2014-09-25 21:26:19 +08:00
|
|
|
ohai line.chomp if options[:print_stdout]
|
2014-07-27 05:29:56 +08:00
|
|
|
end
|
2014-09-25 21:44:24 +08:00
|
|
|
|
|
|
|
while line = raw_stderr.gets
|
|
|
|
processed_stderr << line
|
2014-09-25 21:05:21 +08:00
|
|
|
ohai line.chomp if options[:print_stderr]
|
2014-07-27 05:29:56 +08:00
|
|
|
end
|
|
|
|
|
2014-09-25 21:44:24 +08:00
|
|
|
raw_stdout.close_read
|
|
|
|
raw_stderr.close_read
|
2014-07-27 05:29:56 +08:00
|
|
|
|
2014-12-16 21:50:33 +08:00
|
|
|
processed_status = raw_wait_thr.value
|
2014-09-25 21:44:24 +08:00
|
|
|
|
2014-12-08 19:20:29 +08:00
|
|
|
_assert_success(processed_status, command, processed_stdout) if options[:must_succeed]
|
2014-09-25 21:44:24 +08:00
|
|
|
|
2015-01-01 22:08:06 +08:00
|
|
|
Hbc::SystemCommand::Result.new(command, processed_stdout, processed_stderr, processed_status.exitstatus)
|
2013-05-24 09:46:51 +08:00
|
|
|
end
|
|
|
|
|
2014-02-10 23:53:38 +08:00
|
|
|
def self.run!(command, options={})
|
2013-10-21 05:48:55 +08:00
|
|
|
run(command, options.merge(:must_succeed => true))
|
|
|
|
end
|
|
|
|
|
2014-01-26 04:32:20 +08:00
|
|
|
def self._process_options(executable, options)
|
2014-12-10 07:07:53 +08:00
|
|
|
options.assert_valid_keys :input, :print_stdout, :print_stderr, :args, :must_succeed, :sudo, :bsexec
|
2014-09-25 21:44:24 +08:00
|
|
|
sudo_prefix = %w{/usr/bin/sudo -E --}
|
2014-12-10 07:07:53 +08:00
|
|
|
bsexec_prefix = [ '/bin/launchctl', 'bsexec', options[:bsexec] == :startup ? '/' : options[:bsexec] ]
|
2014-01-26 04:32:20 +08:00
|
|
|
command = [executable]
|
2014-12-10 07:07:53 +08:00
|
|
|
options[:print_stderr] = true if !options.key?(:print_stderr)
|
|
|
|
command.unshift(*bsexec_prefix) if options[:bsexec]
|
|
|
|
command.unshift(*sudo_prefix) if options[:sudo]
|
|
|
|
command.concat(options[:args]) if options.key?(:args) and !options[:args].empty?
|
2013-05-24 09:46:51 +08:00
|
|
|
command
|
|
|
|
end
|
|
|
|
|
2013-10-21 05:48:55 +08:00
|
|
|
def self._assert_success(status, command, output)
|
2014-07-27 05:29:56 +08:00
|
|
|
unless status and status.success?
|
2015-01-01 22:08:06 +08:00
|
|
|
raise Hbc::CaskCommandFailedError.new(command.utf8_inspect, output, status)
|
2014-01-25 01:59:17 +08:00
|
|
|
end
|
2013-04-29 02:56:26 +08:00
|
|
|
end
|
2014-09-25 21:44:24 +08:00
|
|
|
end
|
|
|
|
|
2015-01-01 22:08:06 +08:00
|
|
|
class Hbc::SystemCommand::Result
|
2014-09-25 21:44:24 +08:00
|
|
|
|
|
|
|
attr_accessor :command, :stdout, :stderr, :exit_status
|
|
|
|
|
|
|
|
def initialize(command, stdout, stderr, exit_status)
|
|
|
|
@command = command
|
|
|
|
@stdout = stdout
|
|
|
|
@stderr = stderr
|
|
|
|
@exit_status = exit_status
|
|
|
|
end
|
|
|
|
|
|
|
|
def plist
|
2014-12-27 00:32:03 +08:00
|
|
|
@plist ||= self.class._parse_plist(@command, @stdout.dup)
|
2014-09-25 21:44:24 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def success?
|
|
|
|
@exit_status == 0
|
|
|
|
end
|
|
|
|
|
|
|
|
def merged_output
|
|
|
|
@merged_output ||= @stdout + @stderr
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
@stdout
|
|
|
|
end
|
2013-11-04 03:03:46 +08:00
|
|
|
|
Improve plist handling for `hdiutil` commands
- Raise `CaskError` instead of `Plist::ParseError` from module
- Improve error message when parse result is empty
- remove leading garbage text and emit it to stderr (seen in #5060)
- remove trailing garbage text and emit it to stderr (seen in #4819)
This has the incidental effect of emitting DMG licenses during
installation, which seems desirable as permanent functionality.
If not permanent, the warnings to STDERR should still be kept
temporarily to help get better bug reports on `hdiutil`.
A bug wrt DMG licenses must have been introduced in one of
#4892, #4887, #4889, #4900, #4975, #4978, or #4857. Presumably,
the cause is that STDERR was previously silenced when running
`hdiutil`. It would be cleaner (and more reliable) to redirect
STDERR and examine it separately, rather than clean up the
merged outputs.
closes #4819
closes #5060
2014-06-25 22:30:49 +08:00
|
|
|
def self._warn_plist_garbage(command, garbage)
|
|
|
|
return true unless garbage =~ %r{\S}
|
|
|
|
external = File.basename(command.first)
|
|
|
|
lines = garbage.strip.split("\n")
|
2014-09-25 21:44:24 +08:00
|
|
|
opoo "Non-XML stdout from #{external}:"
|
2016-01-05 07:12:13 +08:00
|
|
|
$stderr.puts lines.map {|l| " #{l}"}
|
Improve plist handling for `hdiutil` commands
- Raise `CaskError` instead of `Plist::ParseError` from module
- Improve error message when parse result is empty
- remove leading garbage text and emit it to stderr (seen in #5060)
- remove trailing garbage text and emit it to stderr (seen in #4819)
This has the incidental effect of emitting DMG licenses during
installation, which seems desirable as permanent functionality.
If not permanent, the warnings to STDERR should still be kept
temporarily to help get better bug reports on `hdiutil`.
A bug wrt DMG licenses must have been introduced in one of
#4892, #4887, #4889, #4900, #4975, #4978, or #4857. Presumably,
the cause is that STDERR was previously silenced when running
`hdiutil`. It would be cleaner (and more reliable) to redirect
STDERR and examine it separately, rather than clean up the
merged outputs.
closes #4819
closes #5060
2014-06-25 22:30:49 +08:00
|
|
|
end
|
|
|
|
|
2013-11-04 03:03:46 +08:00
|
|
|
def self._parse_plist(command, output)
|
|
|
|
begin
|
2015-01-01 22:08:06 +08:00
|
|
|
raise Hbc::CaskError.new("Empty plist input") unless output =~ %r{\S}
|
Improve plist handling for `hdiutil` commands
- Raise `CaskError` instead of `Plist::ParseError` from module
- Improve error message when parse result is empty
- remove leading garbage text and emit it to stderr (seen in #5060)
- remove trailing garbage text and emit it to stderr (seen in #4819)
This has the incidental effect of emitting DMG licenses during
installation, which seems desirable as permanent functionality.
If not permanent, the warnings to STDERR should still be kept
temporarily to help get better bug reports on `hdiutil`.
A bug wrt DMG licenses must have been introduced in one of
#4892, #4887, #4889, #4900, #4975, #4978, or #4857. Presumably,
the cause is that STDERR was previously silenced when running
`hdiutil`. It would be cleaner (and more reliable) to redirect
STDERR and examine it separately, rather than clean up the
merged outputs.
closes #4819
closes #5060
2014-06-25 22:30:49 +08:00
|
|
|
output.sub!(%r{\A(.*?)(<\?\s*xml)}m, '\2')
|
2015-01-01 22:08:06 +08:00
|
|
|
_warn_plist_garbage(command, $1) if Hbc.debug
|
Improve plist handling for `hdiutil` commands
- Raise `CaskError` instead of `Plist::ParseError` from module
- Improve error message when parse result is empty
- remove leading garbage text and emit it to stderr (seen in #5060)
- remove trailing garbage text and emit it to stderr (seen in #4819)
This has the incidental effect of emitting DMG licenses during
installation, which seems desirable as permanent functionality.
If not permanent, the warnings to STDERR should still be kept
temporarily to help get better bug reports on `hdiutil`.
A bug wrt DMG licenses must have been introduced in one of
#4892, #4887, #4889, #4900, #4975, #4978, or #4857. Presumably,
the cause is that STDERR was previously silenced when running
`hdiutil`. It would be cleaner (and more reliable) to redirect
STDERR and examine it separately, rather than clean up the
merged outputs.
closes #4819
closes #5060
2014-06-25 22:30:49 +08:00
|
|
|
output.sub!(%r{(<\s*/\s*plist\s*>)(.*?)\Z}m, '\1')
|
|
|
|
_warn_plist_garbage(command, $2)
|
2014-06-14 22:55:04 +08:00
|
|
|
xml = Plist::parse_xml(output)
|
|
|
|
unless xml.respond_to?(:keys) and xml.keys.size > 0
|
2015-01-01 22:08:06 +08:00
|
|
|
raise Hbc::CaskError.new(<<-ERRMSG)
|
Improve plist handling for `hdiutil` commands
- Raise `CaskError` instead of `Plist::ParseError` from module
- Improve error message when parse result is empty
- remove leading garbage text and emit it to stderr (seen in #5060)
- remove trailing garbage text and emit it to stderr (seen in #4819)
This has the incidental effect of emitting DMG licenses during
installation, which seems desirable as permanent functionality.
If not permanent, the warnings to STDERR should still be kept
temporarily to help get better bug reports on `hdiutil`.
A bug wrt DMG licenses must have been introduced in one of
#4892, #4887, #4889, #4900, #4975, #4978, or #4857. Presumably,
the cause is that STDERR was previously silenced when running
`hdiutil`. It would be cleaner (and more reliable) to redirect
STDERR and examine it separately, rather than clean up the
merged outputs.
closes #4819
closes #5060
2014-06-25 22:30:49 +08:00
|
|
|
Empty result parsing plist output from command.
|
|
|
|
command was:
|
|
|
|
#{command.utf8_inspect}
|
|
|
|
output we attempted to parse:
|
|
|
|
#{output}
|
|
|
|
ERRMSG
|
2014-06-14 22:55:04 +08:00
|
|
|
end
|
|
|
|
xml
|
2014-06-14 21:28:08 +08:00
|
|
|
rescue Plist::ParseError => e
|
2015-01-01 22:08:06 +08:00
|
|
|
raise Hbc::CaskError.new(<<-ERRMSG)
|
2013-11-04 03:03:46 +08:00
|
|
|
Error parsing plist output from command.
|
|
|
|
command was:
|
2014-06-07 05:01:30 +08:00
|
|
|
#{command.utf8_inspect}
|
2014-06-14 21:28:08 +08:00
|
|
|
error was:
|
|
|
|
#{e}
|
2013-11-04 03:03:46 +08:00
|
|
|
output we attempted to parse:
|
|
|
|
#{output}
|
|
|
|
ERRMSG
|
|
|
|
end
|
|
|
|
end
|
2013-04-29 02:56:26 +08:00
|
|
|
end
|