Skip to content

Commit 689da19

Browse files
committed
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.
1 parent e79c4d8 commit 689da19

File tree

4 files changed

+130
-6
lines changed

4 files changed

+130
-6
lines changed

lib/open3.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@
2929
# - Open3.pipeline : run a pipeline and wait for its completion
3030
#
3131

32-
module Open3
33-
VERSION = "0.1.1"
32+
require 'open3/version'
33+
34+
if RUBY_ENGINE == 'jruby' && JRuby::Util::ON_WINDOWS
35+
require_relative 'open3/jruby_windows'
36+
return
37+
end
3438

39+
module Open3
3540
# Open stdin, stdout, and stderr streams and start external executable.
3641
# In addition, a thread to wait for the started process is created.
3742
# The thread has a pid method and a thread variable :pid which is the pid of

lib/open3/jruby_windows.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#
2+
# Custom implementation of Open3.popen{3,2,2e} that uses java.lang.ProcessBuilder rather than pipes and spawns.
3+
#
4+
5+
require 'jruby' # need access to runtime for RubyStatus construction
6+
7+
module Open3
8+
9+
java_import java.lang.ProcessBuilder
10+
java_import org.jruby.RubyProcess
11+
java_import org.jruby.util.ShellLauncher
12+
13+
def popen3(*cmd, &block)
14+
if cmd.size > 0 && Hash === cmd[-1]
15+
opts = cmd.pop
16+
else
17+
opts = {}
18+
end
19+
processbuilder_run(cmd, opts, io: IO_3, &block)
20+
end
21+
module_function :popen3
22+
23+
IO_3 = proc do |process|
24+
[process.getOutputStream.to_io, process.getInputStream.to_io, process.getErrorStream.to_io]
25+
end
26+
27+
BUILD_2 = proc do |builder|
28+
builder.redirectError(ProcessBuilder::Redirect::INHERIT)
29+
end
30+
31+
IO_2 = proc do |process|
32+
[process.getOutputStream.to_io, process.getInputStream.to_io]
33+
end
34+
35+
def popen2(*cmd, &block)
36+
if cmd.size > 0 && Hash === cmd[-1]
37+
opts = cmd.pop
38+
else
39+
opts = {}
40+
end
41+
processbuilder_run(cmd, opts, build: BUILD_2, io: IO_2, &block)
42+
end
43+
module_function :popen2
44+
45+
BUILD_2E = proc do |builder|
46+
builder.redirectErrorStream(true)
47+
end
48+
49+
def popen2e(*cmd, &block)
50+
if cmd.size > 0 && Hash === cmd[-1]
51+
opts = cmd.pop
52+
else
53+
opts = {}
54+
end
55+
processbuilder_run(cmd, opts, build: BUILD_2E, io: IO_2, &block)
56+
end
57+
module_function :popen2e
58+
59+
def processbuilder_run(cmd, opts, build: nil, io:)
60+
if Hash === cmd[0]
61+
env = cmd.shift;
62+
else
63+
env = {}
64+
end
65+
66+
if cmd.size == 1 && (cmd[0] =~ / / || ShellLauncher.shouldUseShell(cmd[0]))
67+
cmd = [RbConfig::CONFIG['SHELL'], JRuby::Util::ON_WINDOWS ? '/c' : '-c', cmd[0]]
68+
end
69+
70+
builder = ProcessBuilder.new(cmd.to_java(:string))
71+
72+
builder.directory(java.io.File.new(opts[:chdir] || Dir.pwd))
73+
74+
environment = builder.environment
75+
env.each { |k, v| v.nil? ? environment.remove(k) : environment.put(k, v) }
76+
77+
build.call(builder) if build
78+
79+
process = builder.start
80+
81+
pid = org.jruby.util.ShellLauncher.getPidFromProcess(process)
82+
83+
parent_io = io.call(process)
84+
85+
parent_io.each {|i| i.sync = true}
86+
87+
wait_thr = DetachThread.new(pid) { RubyProcess::RubyStatus.newProcessStatus(JRuby.runtime, process.waitFor << 8, pid) }
88+
89+
result = [*parent_io, wait_thr]
90+
91+
if defined? yield
92+
begin
93+
return yield(*result)
94+
ensure
95+
parent_io.each(&:close)
96+
wait_thr.join
97+
end
98+
end
99+
100+
result
101+
end
102+
module_function :processbuilder_run
103+
class << self
104+
private :processbuilder_run
105+
end
106+
107+
class DetachThread < Thread
108+
attr_reader :pid
109+
110+
def initialize(pid)
111+
super
112+
113+
@pid = pid
114+
self[:pid] = pid
115+
end
116+
end
117+
118+
end

lib/open3/version.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module Open3
2+
VERSION = "0.1.1"
3+
end

open3.gemspec

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
# frozen_string_literal: true
22

33
name = File.basename(__FILE__, ".gemspec")
4-
version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir|
5-
break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
6-
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
7-
end rescue nil
4+
version = File.foreach(File.join(__dir__, "lib/open3/version.rb")) do |line|
5+
/^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
86
end
97

108
Gem::Specification.new do |spec|

0 commit comments

Comments
 (0)