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

Use an Enum for process stdio redirections #4445

Merged
merged 1 commit into from
Aug 19, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe Process do

it "redirects output to /dev/null" do
# This doesn't test anything but no output should be seen while running tests
Process.run("/bin/ls", output: false).exit_code.should eq(0)
Process.run("/bin/ls", output: Process::Redirect::Close).exit_code.should eq(0)
end

it "gets output" do
Expand Down Expand Up @@ -83,7 +83,7 @@ describe Process do

it "sets working directory" do
parent = File.dirname(Dir.current)
value = Process.run("pwd", shell: true, chdir: parent, output: nil) do |proc|
value = Process.run("pwd", shell: true, chdir: parent, output: Process::Redirect::Pipe) do |proc|
proc.output.gets_to_end
end
value.should eq "#{parent}\n"
Expand All @@ -96,19 +96,19 @@ describe Process do
end

it "looks up programs in the $PATH with a shell" do
proc = Process.run("uname", {"-a"}, shell: true, output: false)
proc = Process.run("uname", {"-a"}, shell: true, output: Process::Redirect::Close)
proc.exit_code.should eq(0)
end

it "allows passing huge argument lists to a shell" do
proc = Process.new(%(echo "${@}"), {"a", "b"}, shell: true, output: nil)
proc = Process.new(%(echo "${@}"), {"a", "b"}, shell: true, output: Process::Redirect::Pipe)
output = proc.output.gets_to_end
proc.wait
output.should eq "a b\n"
end

it "does not run shell code in the argument list" do
proc = Process.new("echo", {"`echo hi`"}, shell: true, output: nil)
proc = Process.new("echo", {"`echo hi`"}, shell: true, output: Process::Redirect::Pipe)
output = proc.output.gets_to_end
proc.wait
output.should eq "`echo hi`\n"
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/codegen/link.cr
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ module Crystal

if libname = attr.lib
if has_pkg_config.nil?
has_pkg_config = Process.run("which", {"pkg-config"}, output: false).success?
has_pkg_config = Process.run("which", {"pkg-config"}, output: Process::Redirect::Close).success?
end

if has_pkg_config && (libflags = pkg_config_flags(libname, attr.static?, library_path))
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class Crystal::Command
status, elapsed_time = @progress_tracker.stage("Execute") do
begin
start_time = Time.now
Process.run(output_filename, args: run_args, input: true, output: true, error: true) do |process|
Process.run(output_filename, args: run_args, input: Process::Redirect::Inherit, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit) do |process|
# Ignore the signal so we don't exit the running process
# (the running process can still handle this signal)
Signal::INT.ignore # do
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/command/deps.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Crystal::Command
error "`shards` executable is missing. Please install shards: https://github.com/crystal-lang/shards"
end

status = Process.run(path_to_shards, args: options, output: true, error: true)
status = Process.run(path_to_shards, args: options, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit)
exit status.exit_code unless status.success?
end
end
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/playground/server.cr
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ module Crystal::Playground
stop_process

@logger.info "Code execution started (session=#{@session_key}, tag=#{tag}, filename=#{output_filename})."
process = @process = Process.new(output_filename, args: [] of String, input: nil, output: nil, error: nil)
process = @process = Process.new(output_filename, args: [] of String, input: Process::Redirect::Pipe, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe)
@running_process_filename = output_filename

spawn do
Expand Down
51 changes: 31 additions & 20 deletions src/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,28 @@ class Process
pid
end

# The standard `IO` configuration of a process:
#
# * `nil`: use a pipe
# * `false`: no `IO` (`/dev/null`)
# * `true`: inherit from parent
# * `IO`: use the given `IO`
alias Stdio = Nil | Bool | IO
# How to redirect the standard input, output and error IO of a process.
enum Redirect
# Pipe the IO so the parent process can read (or write) to the process IO
# throught `#input`, `#output` or `#error`.
Pipe

# Discards the IO.
Close

# Use the IO of the parent process.
Inherit
end

# The standard `IO` configuration of a process.
alias Stdio = Redirect | IO
alias Env = Nil | Hash(String, Nil) | Hash(String, String?) | Hash(String, String)

# Executes a process and waits for it to complete.
#
# By default the process is configured without input, output or error.
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = false, output : Stdio = false, error : Stdio = false, chdir : String? = nil) : Process::Status
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : String? = nil) : Process::Status
status = new(command, args, env, clear_env, shell, input, output, error, chdir).wait
$? = status
status
Expand All @@ -153,7 +162,8 @@ class Process
# will be closed automatically at the end of the block.
#
# Returns the block's value.
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = nil, output : Stdio = nil, error : Stdio = nil, chdir : String? = nil)
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : String? = nil)
process = new(command, args, env, clear_env, shell, input, output, error, chdir)
begin
value = yield process
Expand All @@ -171,7 +181,8 @@ class Process
# * `false`: no `IO` (`/dev/null`)
# * `true`: inherit from parent
# * `IO`: use the given `IO`
def self.exec(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Bool | IO::FileDescriptor = true, output : Bool | IO::FileDescriptor = true, error : Bool | IO::FileDescriptor = true, chdir : String? = nil)
def self.exec(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
input : Stdio = Redirect::Inherit, output : Stdio = Redirect::Inherit, error : Stdio = Redirect::Inherit, chdir : String? = nil)
command, argv = prepare_argv(command, args, shell)
exec_internal(command, argv, env, clear_env, input, output, error, chdir)
end
Expand All @@ -194,14 +205,15 @@ class Process
# To wait for it to finish, invoke `wait`.
#
# By default the process is configured without input, output or error.
def initialize(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = false, output : Stdio = false, error : Stdio = false, chdir : String? = nil)
def initialize(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : String? = nil)
command, argv = Process.prepare_argv(command, args, shell)

@wait_count = 0

if needs_pipe?(input)
fork_input, process_input = IO.pipe(read_blocking: true)
if input
if input.is_a?(IO)
@wait_count += 1
spawn { copy_io(input, process_input, channel, close_dst: true) }
else
Expand All @@ -211,7 +223,7 @@ class Process

if needs_pipe?(output)
process_output, fork_output = IO.pipe(write_blocking: true)
if output
if output.is_a?(IO)
@wait_count += 1
spawn { copy_io(process_output, output, channel, close_src: true) }
else
Expand All @@ -221,7 +233,7 @@ class Process

if needs_pipe?(error)
process_error, fork_error = IO.pipe(write_blocking: true)
if error
if error.is_a?(IO)
@wait_count += 1
spawn { copy_io(process_error, error, channel, close_src: true) }
else
Expand Down Expand Up @@ -334,7 +346,7 @@ class Process
end

private def needs_pipe?(io)
io.nil? || (io.is_a?(IO) && !io.is_a?(IO::FileDescriptor))
(io == Redirect::Pipe) || (io.is_a?(IO) && !io.is_a?(IO::FileDescriptor))
end

private def copy_io(src, dst, channel, close_src = false, close_dst = false)
Expand Down Expand Up @@ -387,10 +399,9 @@ class Process
when IO::FileDescriptor
src_io.blocking = true
dst_io.reopen(src_io)
when true
# use same io as parent
when Redirect::Inherit
dst_io.blocking = true
when false
when Redirect::Close
File.open("/dev/null", mode) do |file|
dst_io.reopen(file)
end
Expand Down Expand Up @@ -431,7 +442,7 @@ end
# LICENSE shard.yml Readme.md spec src
# ```
def system(command : String, args = nil) : Bool
status = Process.run(command, args, shell: true, input: true, output: true, error: true)
status = Process.run(command, args, shell: true, input: Process::Redirect::Inherit, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit)
$? = status
status.success?
end
Expand All @@ -446,7 +457,7 @@ end
# `echo hi` # => "hi\n"
# ```
def `(command) : String
process = Process.new(command, shell: true, input: true, output: nil, error: true)
process = Process.new(command, shell: true, input: Process::Redirect::Inherit, output: Process::Redirect::Pipe, error: Process::Redirect::Inherit)
output = process.output.gets_to_end
status = process.wait
$? = status
Expand Down