summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorCharles Oliver Nutter <headius@headius.com>2021-09-29 13:21:31 -0500
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2021-12-09 19:28:54 +0900
commit01febcab3e6258051e2fc083b906d9ac2bdc3927 (patch)
tree43e34e3794bdbf374d530346578190fffca7ea90 /lib
parent34ebd1392318b45f120f4d598f710bfcf5069c51 (diff)
[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
Diffstat (limited to 'lib')
-rw-r--r--lib/open3.gemspec6
-rw-r--r--lib/open3.rb9
-rw-r--r--lib/open3/jruby_windows.rb118
-rw-r--r--lib/open3/version.rb3
4 files changed, 130 insertions, 6 deletions
diff --git a/lib/open3.gemspec b/lib/open3.gemspec
index ad9485adc7..89c2cbe564 100644
--- a/lib/open3.gemspec
+++ b/lib/open3.gemspec
@@ -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|
diff --git a/lib/open3.rb b/lib/open3.rb
index c574696bb1..2f035e3bcd 100644
--- a/lib/open3.rb
+++ b/lib/open3.rb
@@ -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
diff --git a/lib/open3/jruby_windows.rb b/lib/open3/jruby_windows.rb
new file mode 100644
index 0000000000..24b9a1ba7e
--- /dev/null
+++ b/lib/open3/jruby_windows.rb
@@ -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
diff --git a/lib/open3/version.rb b/lib/open3/version.rb
new file mode 100644
index 0000000000..5a8e84b4ae
--- /dev/null
+++ b/lib/open3/version.rb
@@ -0,0 +1,3 @@
+module Open3
+ VERSION = "0.1.1"
+end