mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
This allows the wrapper functions in the main open3 to be defined
while using our ProcessBuilder logic for the internal popen
implementation.
Note this adds logic to reject redirects from a numeric fd to a
live IO object (or not a String or to_path object) since we cannot
support direct IO redirects with ProcesBuilder.
This patch allows tests to complete with the ProcessBuilder impl.
Only three tests fail:
* test_numeric_file_descriptor2 and test_numeric_file_descriptor2
fail due to redirecting streams to a pipe IO.
* test_pid fails expecting a real PID which we cannot provide via
ProcessBuilder.
73f986c233
127 lines
2.9 KiB
Ruby
127 lines
2.9 KiB
Ruby
#
|
|
# Custom implementation of Open3.popen{3,2,2e} that uses java.lang.ProcessBuilder rather than pipes and spawns.
|
|
#
|
|
|
|
require 'jruby' # need access to runtime for RubyStatus construction
|
|
|
|
module Open3
|
|
|
|
java_import java.lang.ProcessBuilder
|
|
java_import org.jruby.RubyProcess
|
|
java_import org.jruby.util.ShellLauncher
|
|
|
|
def popen3(*cmd, &block)
|
|
if cmd.size > 0 && Hash === cmd[-1]
|
|
opts = cmd.pop
|
|
else
|
|
opts = {}
|
|
end
|
|
processbuilder_run(cmd, opts, io: IO_3, &block)
|
|
end
|
|
module_function :popen3
|
|
|
|
IO_3 = proc do |process|
|
|
[process.getOutputStream.to_io, process.getInputStream.to_io, process.getErrorStream.to_io]
|
|
end
|
|
|
|
BUILD_2 = proc do |builder|
|
|
builder.redirectError(ProcessBuilder::Redirect::INHERIT)
|
|
end
|
|
|
|
IO_2 = proc do |process|
|
|
[process.getOutputStream.to_io, process.getInputStream.to_io]
|
|
end
|
|
|
|
def popen2(*cmd, &block)
|
|
if cmd.size > 0 && Hash === cmd[-1]
|
|
opts = cmd.pop
|
|
else
|
|
opts = {}
|
|
end
|
|
processbuilder_run(cmd, opts, build: BUILD_2, io: IO_2, &block)
|
|
end
|
|
module_function :popen2
|
|
|
|
BUILD_2E = proc do |builder|
|
|
builder.redirectErrorStream(true)
|
|
end
|
|
|
|
def popen2e(*cmd, &block)
|
|
if cmd.size > 0 && Hash === cmd[-1]
|
|
opts = cmd.pop
|
|
else
|
|
opts = {}
|
|
end
|
|
processbuilder_run(cmd, opts, build: BUILD_2E, io: IO_2, &block)
|
|
end
|
|
module_function :popen2e
|
|
|
|
def processbuilder_run(cmd, opts, build: nil, io:)
|
|
opts.each do |k, v|
|
|
if Integer === k
|
|
if IO == v || !(String === v || v.respond_to?(:to_path))
|
|
# target is an open IO or a non-pathable object, bail out
|
|
raise NotImplementedError.new("redirect to an open IO is not implemented on this platform")
|
|
end
|
|
end
|
|
end
|
|
|
|
if Hash === cmd[0]
|
|
env = cmd.shift;
|
|
else
|
|
env = {}
|
|
end
|
|
|
|
if cmd.size == 1 && (cmd[0] =~ / / || ShellLauncher.shouldUseShell(cmd[0]))
|
|
cmd = [RbConfig::CONFIG['SHELL'], JRuby::Util::ON_WINDOWS ? '/c' : '-c', cmd[0]]
|
|
end
|
|
|
|
builder = ProcessBuilder.new(cmd.to_java(:string))
|
|
|
|
builder.directory(java.io.File.new(opts[:chdir] || Dir.pwd))
|
|
|
|
environment = builder.environment
|
|
env.each { |k, v| v.nil? ? environment.remove(k) : environment.put(k, v) }
|
|
|
|
build.call(builder) if build
|
|
|
|
process = builder.start
|
|
|
|
pid = org.jruby.util.ShellLauncher.getPidFromProcess(process)
|
|
|
|
parent_io = io.call(process)
|
|
|
|
parent_io.each {|i| i.sync = true}
|
|
|
|
wait_thr = DetachThread.new(pid) { RubyProcess::RubyStatus.newProcessStatus(JRuby.runtime, process.waitFor << 8, pid) }
|
|
|
|
result = [*parent_io, wait_thr]
|
|
|
|
if defined? yield
|
|
begin
|
|
return yield(*result)
|
|
ensure
|
|
parent_io.each(&:close)
|
|
wait_thr.join
|
|
end
|
|
end
|
|
|
|
result
|
|
end
|
|
module_function :processbuilder_run
|
|
class << self
|
|
private :processbuilder_run
|
|
end
|
|
|
|
class DetachThread < Thread
|
|
attr_reader :pid
|
|
|
|
def initialize(pid)
|
|
super
|
|
|
|
@pid = pid
|
|
self[:pid] = pid
|
|
end
|
|
end
|
|
|
|
end
|