mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
[ruby/open3] Add JRuby's Windows (JDK non-native) Open3 support
This adds JRuby's logic used on platforms where we do not have native access to posix_spawn and related posix functions needed to do fully-native subprocess launching and management. The code here instead uses the JDK ProcessBuilder logic to simulate most of the Open3 functionality. This code does not pass all tests, currently, but provides most of the key functionality on pure-Java (i.e. no native FFI) platforms. https://github.com/ruby/open3/commit/689da19c42
This commit is contained in:
parent
34ebd13923
commit
01febcab3e
4 changed files with 130 additions and 6 deletions
|
@ -1,10 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
name = File.basename(__FILE__, ".gemspec")
|
||||
version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
|
||||
break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
|
||||
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
|
||||
end rescue nil
|
||||
version = File.foreach(File.join(__dir__, "lib/open3/version.rb")) do |line|
|
||||
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
|
||||
end
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
|
|
|
@ -29,9 +29,14 @@
|
|||
# - Open3.pipeline : run a pipeline and wait for its completion
|
||||
#
|
||||
|
||||
module Open3
|
||||
VERSION = "0.1.1"
|
||||
require 'open3/version'
|
||||
|
||||
if RUBY_ENGINE == 'jruby' && JRuby::Util::ON_WINDOWS
|
||||
require_relative 'open3/jruby_windows'
|
||||
return
|
||||
end
|
||||
|
||||
module Open3
|
||||
# Open stdin, stdout, and stderr streams and start external executable.
|
||||
# In addition, a thread to wait for the started process is created.
|
||||
# The thread has a pid method and a thread variable :pid which is the pid of
|
||||
|
|
118
lib/open3/jruby_windows.rb
Normal file
118
lib/open3/jruby_windows.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
#
|
||||
# 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:)
|
||||
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
|
3
lib/open3/version.rb
Normal file
3
lib/open3/version.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
module Open3
|
||||
VERSION = "0.1.1"
|
||||
end
|
Loading…
Reference in a new issue