mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Use SystemCommand
for curl
.
This commit is contained in:
parent
b335a7357b
commit
355df64d93
@ -106,10 +106,10 @@ module Hbc
|
|||||||
LockFile.new(temporary_path.basename).with_lock do
|
LockFile.new(temporary_path.basename).with_lock do
|
||||||
_fetch
|
_fetch
|
||||||
end
|
end
|
||||||
rescue ErrorDuringExecution
|
rescue ErrorDuringExecution => e
|
||||||
# 33 == range not supported
|
# 33 == range not supported
|
||||||
# try wiping the incomplete download and retrying once
|
# try wiping the incomplete download and retrying once
|
||||||
if $CHILD_STATUS.exitstatus == 33 && had_incomplete_download
|
if e.status.exitstatus == 33 && had_incomplete_download
|
||||||
ohai "Trying a full download"
|
ohai "Trying a full download"
|
||||||
temporary_path.unlink
|
temporary_path.unlink
|
||||||
had_incomplete_download = false
|
had_incomplete_download = false
|
||||||
|
@ -526,7 +526,11 @@ end
|
|||||||
|
|
||||||
# raised by safe_system in utils.rb
|
# raised by safe_system in utils.rb
|
||||||
class ErrorDuringExecution < RuntimeError
|
class ErrorDuringExecution < RuntimeError
|
||||||
|
attr_reader :status
|
||||||
|
|
||||||
def initialize(cmd, status:, output: nil)
|
def initialize(cmd, status:, output: nil)
|
||||||
|
@status = status
|
||||||
|
|
||||||
s = "Failure while executing; `#{cmd.shelljoin.gsub(/\\=/, "=")}` exited with #{status.exitstatus}."
|
s = "Failure while executing; `#{cmd.shelljoin.gsub(/\\=/, "=")}` exited with #{status.exitstatus}."
|
||||||
|
|
||||||
unless [*output].empty?
|
unless [*output].empty?
|
||||||
|
@ -37,7 +37,13 @@ class SystemCommand
|
|||||||
puts line.chomp if print_stdout?
|
puts line.chomp if print_stdout?
|
||||||
@merged_output << [:stdout, line]
|
@merged_output << [:stdout, line]
|
||||||
when :stderr
|
when :stderr
|
||||||
$stderr.puts Formatter.error(line.chomp) if print_stderr?
|
if print_stderr?
|
||||||
|
if line.start_with?("\r")
|
||||||
|
$stderr.print line
|
||||||
|
else
|
||||||
|
$stderr.puts Formatter.error(line.chomp)
|
||||||
|
end
|
||||||
|
end
|
||||||
@merged_output << [:stderr, line]
|
@merged_output << [:stderr, line]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -76,13 +82,18 @@ class SystemCommand
|
|||||||
def env_args
|
def env_args
|
||||||
return [] if env.empty?
|
return [] if env.empty?
|
||||||
|
|
||||||
variables = env.map do |name, value|
|
unset_variables = env.select { |_, value| value.nil? }
|
||||||
|
.flat_map { |name,| ["-u", name] }
|
||||||
|
|
||||||
|
set_variables =
|
||||||
|
env.reject { |_, value| value.nil? }
|
||||||
|
.flat_map do |name, value|
|
||||||
sanitized_name = Shellwords.escape(name)
|
sanitized_name = Shellwords.escape(name)
|
||||||
sanitized_value = Shellwords.escape(value)
|
sanitized_value = Shellwords.escape(value)
|
||||||
"#{sanitized_name}=#{sanitized_value}"
|
"#{sanitized_name}=#{sanitized_value}"
|
||||||
end
|
end
|
||||||
|
|
||||||
["env", *variables]
|
["env", *unset_variables, *set_variables]
|
||||||
end
|
end
|
||||||
|
|
||||||
def sudo_prefix
|
def sudo_prefix
|
||||||
@ -102,7 +113,7 @@ class SystemCommand
|
|||||||
@expanded_args ||= args.map do |arg|
|
@expanded_args ||= args.map do |arg|
|
||||||
if arg.respond_to?(:to_path)
|
if arg.respond_to?(:to_path)
|
||||||
File.absolute_path(arg)
|
File.absolute_path(arg)
|
||||||
elsif arg.is_a?(Integer) || arg.is_a?(Float)
|
elsif arg.is_a?(Integer) || arg.is_a?(Float) || arg.is_a?(URI)
|
||||||
arg.to_s
|
arg.to_s
|
||||||
else
|
else
|
||||||
arg.to_str
|
arg.to_str
|
||||||
@ -157,23 +168,28 @@ class SystemCommand
|
|||||||
hash[type] << line
|
hash[type] << line
|
||||||
end
|
end
|
||||||
|
|
||||||
Result.new(command, output[:stdout], output[:stderr], @status.exitstatus)
|
Result.new(command, output[:stdout], output[:stderr], @status)
|
||||||
end
|
end
|
||||||
|
|
||||||
class Result
|
class Result
|
||||||
attr_accessor :command, :stdout, :stderr, :exit_status
|
attr_accessor :command, :stdout, :stderr, :status, :exit_status
|
||||||
|
|
||||||
def initialize(command, stdout, stderr, exit_status)
|
def initialize(command, stdout, stderr, status)
|
||||||
@command = command
|
@command = command
|
||||||
@stdout = stdout
|
@stdout = stdout
|
||||||
@stderr = stderr
|
@stderr = stderr
|
||||||
@exit_status = exit_status
|
@status = status
|
||||||
|
@exit_status = status.exitstatus
|
||||||
end
|
end
|
||||||
|
|
||||||
def success?
|
def success?
|
||||||
@exit_status.zero?
|
@exit_status.zero?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_ary
|
||||||
|
[stdout, stderr, status]
|
||||||
|
end
|
||||||
|
|
||||||
def plist
|
def plist
|
||||||
@plist ||= begin
|
@plist ||= begin
|
||||||
output = stdout
|
output = stdout
|
||||||
|
@ -39,7 +39,7 @@ describe "download strategies", :cask do
|
|||||||
let(:url_options) { { user_agent: "Mozilla/25.0.1" } }
|
let(:url_options) { { user_agent: "Mozilla/25.0.1" } }
|
||||||
|
|
||||||
it "adds the appropriate curl args" do
|
it "adds the appropriate curl args" do
|
||||||
expect(downloader).to receive(:safe_system) { |*args|
|
expect(downloader).to receive(:system_command!) { |*, args:, **|
|
||||||
expect(args.each_cons(2)).to include(["--user-agent", "Mozilla/25.0.1"])
|
expect(args.each_cons(2)).to include(["--user-agent", "Mozilla/25.0.1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ describe "download strategies", :cask do
|
|||||||
let(:url_options) { { user_agent: :fake } }
|
let(:url_options) { { user_agent: :fake } }
|
||||||
|
|
||||||
it "adds the appropriate curl args" do
|
it "adds the appropriate curl args" do
|
||||||
expect(downloader).to receive(:safe_system) { |*args|
|
expect(downloader).to receive(:system_command!) { |*, args:, **|
|
||||||
expect(args.each_cons(2).to_a).to include(["--user-agent", a_string_matching(/Mozilla.*Mac OS X 10.*AppleWebKit/)])
|
expect(args.each_cons(2).to_a).to include(["--user-agent", a_string_matching(/Mozilla.*Mac OS X 10.*AppleWebKit/)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ describe Hbc::Pkg, :cask do
|
|||||||
"/usr/sbin/pkgutil",
|
"/usr/sbin/pkgutil",
|
||||||
args: ["--pkg-info-plist", pkg_id],
|
args: ["--pkg-info-plist", pkg_id],
|
||||||
).and_return(
|
).and_return(
|
||||||
SystemCommand::Result.new(nil, pkg_info_plist, nil, 0),
|
SystemCommand::Result.new(nil, pkg_info_plist, nil, instance_double(Process::Status, exitstatus: 0)),
|
||||||
)
|
)
|
||||||
|
|
||||||
info = pkg.info
|
info = pkg.info
|
||||||
|
@ -52,7 +52,7 @@ class FakeSystemCommand
|
|||||||
if response.respond_to?(:call)
|
if response.respond_to?(:call)
|
||||||
response.call(command_string, options)
|
response.call(command_string, options)
|
||||||
else
|
else
|
||||||
SystemCommand::Result.new(command, response, "", 0)
|
SystemCommand::Result.new(command, response, "", OpenStruct.new(exitstatus: 0))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
require "system_command"
|
require "system_command"
|
||||||
|
|
||||||
describe SystemCommand::Result do
|
describe SystemCommand::Result do
|
||||||
|
describe "#to_ary" do
|
||||||
|
subject(:result) {
|
||||||
|
described_class.new([], "output", "error", instance_double(Process::Status, exitstatus: 0, success?: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
it "can be destructed like `Open3.capture3`" do
|
||||||
|
out, err, status = result
|
||||||
|
|
||||||
|
expect(out).to eq "output"
|
||||||
|
expect(err).to eq "error"
|
||||||
|
expect(status).to be_a_success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#plist" do
|
describe "#plist" do
|
||||||
subject { described_class.new(command, stdout, "", 0).plist }
|
subject { described_class.new(command, stdout, "", instance_double(Process::Status, exitstatus: 0)).plist }
|
||||||
|
|
||||||
let(:command) { ["true"] }
|
let(:command) { ["true"] }
|
||||||
let(:garbage) {
|
let(:garbage) {
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
describe SystemCommand do
|
describe SystemCommand do
|
||||||
describe "#initialize" do
|
describe "#initialize" do
|
||||||
let(:env_args) { ["bash", "-c", 'printf "%s" "${A?}" "${B?}" "${C?}"'] }
|
let(:env_args) { ["bash", "-c", 'printf "%s" "${A?}" "${B?}" "${C?}"'] }
|
||||||
|
let(:env) { { "A" => "1", "B" => "2", "C" => "3" } }
|
||||||
|
let(:sudo) { false }
|
||||||
|
|
||||||
context "when given some environment variables" do
|
subject(:command) {
|
||||||
subject {
|
|
||||||
described_class.new(
|
described_class.new(
|
||||||
"env",
|
"env",
|
||||||
args: env_args,
|
args: env_args,
|
||||||
env: { "A" => "1", "B" => "2", "C" => "3" },
|
env: env,
|
||||||
must_succeed: true,
|
must_succeed: true,
|
||||||
|
sudo: sudo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context "when given some environment variables" do
|
||||||
its("run!.stdout") { is_expected.to eq("123") }
|
its("run!.stdout") { is_expected.to eq("123") }
|
||||||
|
|
||||||
describe "the resulting command line" do
|
describe "the resulting command line" do
|
||||||
@ -21,21 +24,23 @@ describe SystemCommand do
|
|||||||
.with(["env", "env"], "A=1", "B=2", "C=3", "env", *env_args, {})
|
.with(["env", "env"], "A=1", "B=2", "C=3", "env", *env_args, {})
|
||||||
.and_call_original
|
.and_call_original
|
||||||
|
|
||||||
subject.run!
|
command.run!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when given an environment variable which is set to nil" do
|
||||||
|
let(:env) { { "A" => "1", "B" => "2", "C" => nil } }
|
||||||
|
|
||||||
|
it "unsets them" do
|
||||||
|
expect {
|
||||||
|
command.run!
|
||||||
|
}.to raise_error(/C: parameter null or not set/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "when given some environment variables and sudo: true" do
|
context "when given some environment variables and sudo: true" do
|
||||||
subject {
|
let(:sudo) { true }
|
||||||
described_class.new(
|
|
||||||
"env",
|
|
||||||
args: env_args,
|
|
||||||
env: { "A" => "1", "B" => "2", "C" => "3" },
|
|
||||||
must_succeed: true,
|
|
||||||
sudo: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe "the resulting command line" do
|
describe "the resulting command line" do
|
||||||
it "includes the given variables explicitly" do
|
it "includes the given variables explicitly" do
|
||||||
@ -47,7 +52,7 @@ describe SystemCommand do
|
|||||||
original_popen3.call("true", &block)
|
original_popen3.call("true", &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
subject.run!
|
command.run!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -215,5 +220,12 @@ describe SystemCommand do
|
|||||||
described_class.run("non_existent_executable")
|
described_class.run("non_existent_executable")
|
||||||
}.not_to raise_error
|
}.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not format `stderr` when it starts with \r' do
|
||||||
|
expect {
|
||||||
|
system_command "bash",
|
||||||
|
args: ["-c", 'printf "\r%s" "################### 27.6%" 1>&2']
|
||||||
|
}.to output("\r################### 27.6%").to_stderr
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,12 +4,12 @@ describe "curl" do
|
|||||||
describe "curl_args" do
|
describe "curl_args" do
|
||||||
it "returns -q as the first argument when HOMEBREW_CURLRC is not set" do
|
it "returns -q as the first argument when HOMEBREW_CURLRC is not set" do
|
||||||
# -q must be the first argument according to "man curl"
|
# -q must be the first argument according to "man curl"
|
||||||
expect(curl_args("foo")[1]).to eq("-q")
|
expect(curl_args("foo").first).to eq("-q")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't return -q as the first argument when HOMEBREW_CURLRC is set" do
|
it "doesn't return -q as the first argument when HOMEBREW_CURLRC is set" do
|
||||||
ENV["HOMEBREW_CURLRC"] = "1"
|
ENV["HOMEBREW_CURLRC"] = "1"
|
||||||
expect(curl_args("foo")[1]).to_not eq("-q")
|
expect(curl_args("foo").first).to_not eq("-q")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -11,7 +11,7 @@ def curl_executable
|
|||||||
end
|
end
|
||||||
|
|
||||||
def curl_args(*extra_args, show_output: false, user_agent: :default)
|
def curl_args(*extra_args, show_output: false, user_agent: :default)
|
||||||
args = [curl_executable.to_s]
|
args = []
|
||||||
|
|
||||||
# do not load .curlrc unless requested (must be the first argument)
|
# do not load .curlrc unless requested (must be the first argument)
|
||||||
args << "-q" unless ENV["HOMEBREW_CURLRC"]
|
args << "-q" unless ENV["HOMEBREW_CURLRC"]
|
||||||
@ -40,18 +40,18 @@ end
|
|||||||
def curl(*args)
|
def curl(*args)
|
||||||
# SSL_CERT_FILE can be incorrectly set by users or portable-ruby and screw
|
# SSL_CERT_FILE can be incorrectly set by users or portable-ruby and screw
|
||||||
# with SSL downloads so unset it here.
|
# with SSL downloads so unset it here.
|
||||||
with_env SSL_CERT_FILE: nil do
|
system_command! curl_executable,
|
||||||
safe_system(*curl_args(*args))
|
args: curl_args(*args),
|
||||||
end
|
env: { "SSL_CERT_FILE" => nil }
|
||||||
end
|
end
|
||||||
|
|
||||||
def curl_download(*args, to: nil, continue_at: "-", **options)
|
def curl_download(*args, to: nil, continue_at: "-", **options)
|
||||||
had_incomplete_download ||= File.exist?(to)
|
had_incomplete_download ||= File.exist?(to)
|
||||||
curl("--location", "--remote-time", "--continue-at", continue_at.to_s, "--output", to, *args, **options)
|
curl("--location", "--remote-time", "--continue-at", continue_at.to_s, "--output", to, *args, **options)
|
||||||
rescue ErrorDuringExecution
|
rescue ErrorDuringExecution => e
|
||||||
# `curl` error 33: HTTP server doesn't seem to support byte ranges. Cannot resume.
|
# `curl` error 33: HTTP server doesn't seem to support byte ranges. Cannot resume.
|
||||||
# HTTP status 416: Requested range not satisfiable
|
# HTTP status 416: Requested range not satisfiable
|
||||||
if ($CHILD_STATUS.exitstatus == 33 || had_incomplete_download) && continue_at == "-"
|
if (e.status.exitstatus == 33 || had_incomplete_download) && continue_at == "-"
|
||||||
continue_at = 0
|
continue_at = 0
|
||||||
had_incomplete_download = false
|
had_incomplete_download = false
|
||||||
retry
|
retry
|
||||||
@ -61,7 +61,9 @@ rescue ErrorDuringExecution
|
|||||||
end
|
end
|
||||||
|
|
||||||
def curl_output(*args, **options)
|
def curl_output(*args, **options)
|
||||||
Open3.capture3(*curl_args(*args, show_output: true, **options))
|
system_command(curl_executable,
|
||||||
|
args: curl_args(*args, show_output: true, **options),
|
||||||
|
print_stderr: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def curl_check_http_content(url, user_agents: [:default], check_content: false, strict: false, require_http: false)
|
def curl_check_http_content(url, user_agents: [:default], check_content: false, strict: false, require_http: false)
|
||||||
|
@ -131,13 +131,12 @@ module GitHub
|
|||||||
# This is a no-op if the user is opting out of using the GitHub API.
|
# This is a no-op if the user is opting out of using the GitHub API.
|
||||||
return block_given? ? yield({}) : {} if ENV["HOMEBREW_NO_GITHUB_API"]
|
return block_given? ? yield({}) : {} if ENV["HOMEBREW_NO_GITHUB_API"]
|
||||||
|
|
||||||
args = %W[--header application/vnd.github.v3+json --write-out \n%{http_code}] # rubocop:disable Lint/NestedPercentLiteral
|
args = ["--header", "application/vnd.github.v3+json", "--write-out", "\n%{http_code}"]
|
||||||
args += curl_args
|
|
||||||
|
|
||||||
token, username = api_credentials
|
token, username = api_credentials
|
||||||
case api_credentials_type
|
case api_credentials_type
|
||||||
when :keychain
|
when :keychain
|
||||||
args += %W[--user #{username}:#{token}]
|
args += ["--user", "#{username}:#{token}"]
|
||||||
when :environment
|
when :environment
|
||||||
args += ["--header", "Authorization: token #{token}"]
|
args += ["--header", "Authorization: token #{token}"]
|
||||||
end
|
end
|
||||||
@ -162,7 +161,7 @@ module GitHub
|
|||||||
|
|
||||||
args += ["--dump-header", headers_tmpfile.path]
|
args += ["--dump-header", headers_tmpfile.path]
|
||||||
|
|
||||||
output, errors, status = curl_output(url.to_s, "--location", *args)
|
output, errors, status = curl_output("--location", url.to_s, *args)
|
||||||
output, _, http_code = output.rpartition("\n")
|
output, _, http_code = output.rpartition("\n")
|
||||||
output, _, http_code = output.rpartition("\n") if http_code == "000"
|
output, _, http_code = output.rpartition("\n") if http_code == "000"
|
||||||
headers = headers_tmpfile.read
|
headers = headers_tmpfile.read
|
||||||
|
Loading…
x
Reference in New Issue
Block a user