childprocess-5.1.0/0000755000004100000410000000000014670202331014225 5ustar www-datawww-datachildprocess-5.1.0/.gitignore0000644000004100000410000000031714670202331016216 0ustar www-datawww-data## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.swp ## RubyMine .idea/* ## PROJECT::GENERAL coverage rdoc pkg .rbx Gemfile.lock .ruby-version .bundle ## PROJECT::SPECIFIC childprocess-5.1.0/.document0000644000004100000410000000007614670202331016047 0ustar www-datawww-dataREADME.rdoc lib/**/*.rb bin/* features/**/*.feature - LICENSE childprocess-5.1.0/.github/0000755000004100000410000000000014670202331015565 5ustar www-datawww-datachildprocess-5.1.0/.github/workflows/0000755000004100000410000000000014670202331017622 5ustar www-datawww-datachildprocess-5.1.0/.github/workflows/ci.yml0000644000004100000410000000232014670202331020735 0ustar www-datawww-dataname: CI on: [push, pull_request] jobs: test: strategy: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] ruby: [ '2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', head, jruby, truffleruby ] # CRuby < 2.6 does not support macos-arm64, so test those on amd64 instead # JRuby 9.4.7.0 does not have native console support on macos-arm64: https://github.com/jruby/jruby/issues/8271 include: - { os: macos-13, ruby: '2.4' } - { os: macos-13, ruby: '2.5' } - { os: macos-13, ruby: jruby } exclude: - { os: macos-latest, ruby: '2.4' } - { os: macos-latest, ruby: '2.5' } - { os: macos-latest, ruby: jruby } - { os: windows-latest, ruby: truffleruby } # fails to load rspec: RuntimeError: CRITICAL: RUBYGEMS_ACTIVATION_MONITOR.owned?: before false -> after true - { os: windows-latest, ruby: jruby } runs-on: ${{ matrix.os }} env: CHILDPROCESS_UNSET: should-be-unset steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake spec childprocess-5.1.0/lib/0000755000004100000410000000000014670202331014773 5ustar www-datawww-datachildprocess-5.1.0/lib/childprocess/0000755000004100000410000000000014670202331017455 5ustar www-datawww-datachildprocess-5.1.0/lib/childprocess/windows/0000755000004100000410000000000014670202331021147 5ustar www-datawww-datachildprocess-5.1.0/lib/childprocess/windows/process.rb0000644000004100000410000000111114670202331023144 0ustar www-datawww-datarequire_relative '../process_spawn_process' module ChildProcess module Windows class Process < ProcessSpawnProcess def io @io ||= Windows::IO.new end def stop(timeout = 3) assert_started send_kill begin return poll_for_exit(timeout) rescue TimeoutError # try next end wait rescue Errno::ECHILD, Errno::ESRCH # handle race condition where process dies between timeout # and send_kill true end end # Process end # Windows end # ChildProcess childprocess-5.1.0/lib/childprocess/windows/io.rb0000644000004100000410000000075514670202331022112 0ustar www-datawww-datamodule ChildProcess module Windows class IO < AbstractIO private def check_type(io) return if has_fileno?(io) return if has_to_io?(io) raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io" end def has_fileno?(io) io.respond_to?(:fileno) && io.fileno end def has_to_io?(io) io.respond_to?(:to_io) && io.to_io.kind_of?(::IO) end end # IO end # Windows end # ChildProcess childprocess-5.1.0/lib/childprocess/unix.rb0000644000004100000410000000015014670202331020761 0ustar www-datawww-datamodule ChildProcess module Unix end end require_relative "unix/io" require_relative "unix/process" childprocess-5.1.0/lib/childprocess/windows.rb0000644000004100000410000000024614670202331021476 0ustar www-datawww-datarequire "rbconfig" module ChildProcess module Windows end # Windows end # ChildProcess require "childprocess/windows/io" require "childprocess/windows/process" childprocess-5.1.0/lib/childprocess/unix/0000755000004100000410000000000014670202331020440 5ustar www-datawww-datachildprocess-5.1.0/lib/childprocess/unix/process.rb0000644000004100000410000000112214670202331022437 0ustar www-datawww-datarequire_relative '../process_spawn_process' module ChildProcess module Unix class Process < ProcessSpawnProcess def io @io ||= Unix::IO.new end def stop(timeout = 3) assert_started send_term begin return poll_for_exit(timeout) rescue TimeoutError # try next end send_kill wait rescue Errno::ECHILD, Errno::ESRCH # handle race condition where process dies between timeout # and send_kill true end end # Process end # Unix end # ChildProcess childprocess-5.1.0/lib/childprocess/unix/io.rb0000644000004100000410000000070214670202331021373 0ustar www-datawww-datamodule ChildProcess module Unix class IO < AbstractIO private def check_type(io) unless io.respond_to? :to_io raise ArgumentError, "expected #{io.inspect} to respond to :to_io" end result = io.to_io unless result && result.kind_of?(::IO) raise TypeError, "expected IO, got #{result.inspect}:#{result.class}" end end end # IO end # Unix end # ChildProcess childprocess-5.1.0/lib/childprocess/process_spawn_process.rb0000644000004100000410000000544014670202331024431 0ustar www-datawww-datarequire_relative 'abstract_process' module ChildProcess class ProcessSpawnProcess < AbstractProcess attr_reader :pid def exited? return true if @exit_code assert_started pid, status = ::Process.waitpid2(@pid, ::Process::WNOHANG | ::Process::WUNTRACED) pid = nil if pid == 0 # may happen on jruby log(:pid => pid, :status => status) if pid set_exit_code(status) end !!pid rescue Errno::ECHILD # may be thrown for detached processes true end def wait assert_started if exited? exit_code else _, status = ::Process.waitpid2(@pid) set_exit_code(status) end end private def launch_process environment = {} @environment.each_pair do |key, value| key = key.to_s value = value.nil? ? nil : value.to_s if key.include?("\0") || key.include?("=") || value.to_s.include?("\0") raise InvalidEnvironmentVariable, "#{key.inspect} => #{value.to_s.inspect}" end environment[key] = value end options = {} options[:out] = io.stdout ? io.stdout.fileno : File::NULL options[:err] = io.stderr ? io.stderr.fileno : File::NULL if duplex? reader, writer = ::IO.pipe options[:in] = reader.fileno unless ChildProcess.windows? options[writer.fileno] = :close end end if leader? if ChildProcess.windows? options[:new_pgroup] = true else options[:pgroup] = true end end options[:chdir] = @cwd if @cwd if @args.size == 1 # When given a single String, Process.spawn would think it should use the shell # if there is any special character in it. However, ChildProcess should never # use the shell. So we use the [cmdname, argv0] form to force no shell. arg = @args[0] args = [[arg, arg]] else args = @args end begin @pid = ::Process.spawn(environment, *args, options) rescue SystemCallError => e raise LaunchError, e.message end if duplex? io._stdin = writer reader.close end ::Process.detach(@pid) if detach? end def set_exit_code(status) @exit_code = status.exitstatus || status.termsig end def send_term send_signal 'TERM' end def send_kill send_signal 'KILL' end def send_signal(sig) assert_started log "sending #{sig}" if leader? if ChildProcess.unix? ::Process.kill sig, -@pid # negative pid == process group else output = `taskkill /F /T /PID #{@pid}` log output end else ::Process.kill sig, @pid end end end end childprocess-5.1.0/lib/childprocess/abstract_process.rb0000644000004100000410000000705014670202331023345 0ustar www-datawww-datamodule ChildProcess class AbstractProcess POLL_INTERVAL = 0.1 attr_reader :exit_code # # Set this to true if you do not care about when or if the process quits. # attr_accessor :detach # # Set this to true if you want to write to the process' stdin (process.io.stdin) # attr_accessor :duplex # # Modify the child's environment variables # attr_reader :environment # # Set the child's current working directory. # attr_accessor :cwd # # Set this to true to make the child process the leader of a new process group # # This can be used to make sure that all grandchildren are killed # when the child process dies. # attr_accessor :leader # # Create a new process with the given args. # # @api private # @see ChildProcess.build # def initialize(*args) unless args.all? { |e| e.kind_of?(String) } raise ArgumentError, "all arguments must be String: #{args.inspect}" end @args = args @started = false @exit_code = nil @io = nil @cwd = nil @detach = false @duplex = false @leader = false @environment = {} end # # Returns a ChildProcess::AbstractIO subclass to configure the child's IO streams. # def io raise SubclassResponsibility, "io" end # # @return [Integer] the pid of the process after it has started # def pid raise SubclassResponsibility, "pid" end # # Launch the child process # # @return [AbstractProcess] self # def start launch_process @started = true self end # # Forcibly terminate the process, using increasingly harsher methods if possible. # # @param [Integer] timeout (3) Seconds to wait before trying the next method. # def stop(timeout = 3) raise SubclassResponsibility, "stop" end # # Block until the process has been terminated. # # @return [Integer] The exit status of the process # def wait raise SubclassResponsibility, "wait" end # # Did the process exit? # # @return [Boolean] # def exited? raise SubclassResponsibility, "exited?" end # # Has the process started? # # @return [Boolean] # def started? @started end # # Is this process running? # # @return [Boolean] # def alive? started? && !exited? end # # Returns true if the process has exited and the exit code was not 0. # # @return [Boolean] # def crashed? exited? && @exit_code != 0 end # # Wait for the process to exit, raising a ChildProcess::TimeoutError if # the timeout expires. # def poll_for_exit(timeout) log "polling #{timeout} seconds for exit" end_time = Time.now + timeout until (ok = exited?) || Time.now > end_time sleep POLL_INTERVAL end unless ok raise TimeoutError, "process still alive after #{timeout} seconds" end end private def launch_process raise SubclassResponsibility, "launch_process" end def detach? @detach end def duplex? @duplex end def leader? @leader end def log(*args) ChildProcess.logger.debug "#{self.inspect} : #{args.inspect}" end def assert_started raise Error, "process not started" unless started? end end # AbstractProcess end # ChildProcess childprocess-5.1.0/lib/childprocess/version.rb0000644000004100000410000000005414670202331021466 0ustar www-datawww-datamodule ChildProcess VERSION = '5.1.0' end childprocess-5.1.0/lib/childprocess/errors.rb0000644000004100000410000000034314670202331021316 0ustar www-datawww-datamodule ChildProcess class Error < StandardError end class TimeoutError < Error end class SubclassResponsibility < Error end class InvalidEnvironmentVariable < Error end class LaunchError < Error end end childprocess-5.1.0/lib/childprocess/abstract_io.rb0000644000004100000410000000075414670202331022302 0ustar www-datawww-datamodule ChildProcess class AbstractIO attr_reader :stderr, :stdout, :stdin def inherit! @stdout = STDOUT @stderr = STDERR end def stderr=(io) check_type io @stderr = io end def stdout=(io) check_type io @stdout = io end # # @api private # def _stdin=(io) check_type io @stdin = io end private def check_type(io) raise SubclassResponsibility, "check_type" end end end childprocess-5.1.0/lib/childprocess.rb0000644000004100000410000000720014670202331020001 0ustar www-datawww-datarequire 'childprocess/version' require 'childprocess/errors' require 'childprocess/abstract_process' require 'childprocess/abstract_io' require 'childprocess/process_spawn_process' require "fcntl" require 'logger' module ChildProcess @posix_spawn = false class << self attr_writer :logger def new(*args) case os when :macosx, :linux, :solaris, :bsd, :cygwin, :aix Unix::Process.new(*args) when :windows Windows::Process.new(*args) else raise Error, "unsupported platform #{platform_name.inspect}" end end alias_method :build, :new def logger return @logger if defined?(@logger) and @logger @logger = Logger.new($stderr) @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO @logger end def platform os end def platform_name @platform_name ||= "#{arch}-#{os}" end def unix? !windows? end def linux? os == :linux end def jruby? RUBY_ENGINE == 'jruby' end def windows? os == :windows end def posix_spawn_chosen_explicitly? @posix_spawn || %w[1 true].include?(ENV['CHILDPROCESS_POSIX_SPAWN']) end def posix_spawn? false end # # Set this to true to enable experimental use of posix_spawn. # def posix_spawn=(bool) @posix_spawn = bool end def os return :windows if ENV['FAKE_WINDOWS'] == 'true' @os ||= ( require "rbconfig" host_os = RbConfig::CONFIG['host_os'].downcase case host_os when /linux/ :linux when /darwin|mac os/ :macosx when /mswin|msys|mingw32/ :windows when /cygwin/ :cygwin when /solaris|sunos/ :solaris when /bsd|dragonfly/ :bsd when /aix/ :aix else raise Error, "unknown os: #{host_os.inspect}" end ) end def arch @arch ||= ( host_cpu = RbConfig::CONFIG['host_cpu'].downcase case host_cpu when /i[3456]86/ if workaround_older_macosx_misreported_cpu? # Workaround case: older 64-bit Darwin Rubies misreported as i686 "x86_64" else "i386" end when /amd64|x86_64/ "x86_64" when /ppc|powerpc/ "powerpc" else host_cpu end ) end # # By default, a child process will inherit open file descriptors from the # parent process. This helper provides a cross-platform way of making sure # that doesn't happen for the given file/io. # def close_on_exec(file) if file.respond_to?(:close_on_exec=) file.close_on_exec = true else raise Error, "not sure how to set close-on-exec for #{file.inspect} on #{platform_name.inspect}" end end private def warn_once(msg) @warnings ||= {} unless @warnings[msg] @warnings[msg] = true logger.warn msg end end # Workaround: detect the situation that an older Darwin Ruby is actually # 64-bit, but is misreporting cpu as i686, which would imply 32-bit. # # @return [Boolean] `true` if: # (a) on Mac OS X # (b) actually running in 64-bit mode def workaround_older_macosx_misreported_cpu? os == :macosx && is_64_bit? end # @return [Boolean] `true` if this Ruby represents `1` in 64 bits (8 bytes). def is_64_bit? 1.size == 8 end end # class << self end # ChildProcess if ChildProcess.windows? require 'childprocess/windows' else require 'childprocess/unix' end childprocess-5.1.0/spec/0000755000004100000410000000000014670202331015157 5ustar www-datawww-datachildprocess-5.1.0/spec/spec_helper.rb0000644000004100000410000001166614670202331020007 0ustar www-datawww-data$LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) unless defined?(JRUBY_VERSION) require 'coveralls' Coveralls.wear! end require 'childprocess' require 'rspec' require 'tempfile' require 'socket' require 'stringio' module ChildProcessSpecHelper RUBY = defined?(Gem) ? Gem.ruby : 'ruby' CapturedOutput = Struct.new(:stdout, :stderr) def ruby_process(*args) @process = ChildProcess.build(RUBY , *args) end def windows_process(*args) @process = ChildProcess.build("powershell", *args) end def sleeping_ruby(seconds = nil) if seconds ruby_process("-e", "sleep #{seconds}") else ruby_process("-e", "sleep") end end def invalid_process @process = ChildProcess.build("unlikelytoexist") end def ignored(signal) code = <<-RUBY trap(#{signal.inspect}, "IGNORE") sleep RUBY ruby_process tmp_script(code) end def write_env(path) if ChildProcess.os == :windows ps_env_file_path = File.expand_path(File.dirname(__FILE__)) args = ['-File', "#{ps_env_file_path}/get_env.ps1", path] windows_process(*args) else code = <<-RUBY File.open(#{path.inspect}, "w") { |f| f << ENV.inspect } RUBY ruby_process tmp_script(code) end end def write_argv(path, *args) code = <<-RUBY File.open(#{path.inspect}, "w") { |f| f << ARGV.inspect } RUBY ruby_process(tmp_script(code), *args) end def write_pid(path) code = <<-RUBY File.open(#{path.inspect}, "w") { |f| f << Process.pid } RUBY ruby_process tmp_script(code) end def write_pid_in_sleepy_grand_child(path) code = <<-RUBY system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid; f.flush }; sleep' RUBY ruby_process tmp_script(code) end def exit_with(exit_code) ruby_process(tmp_script("exit(#{exit_code})")) end def with_env(hash) hash.each { |k,v| ENV[k] = v } begin yield ensure hash.each_key { |k| ENV[k] = nil } end end def tmp_script(code) # use an ivar to avoid GC @tf = Tempfile.new("childprocess-temp") @tf << code @tf.close puts code if $DEBUG @tf.path end def cat if ChildProcess.os == :windows ruby(<<-CODE) STDIN.sync = STDOUT.sync = true IO.copy_stream(STDIN, STDOUT) CODE else ChildProcess.build("cat") end end def echo if ChildProcess.os == :windows ruby(<<-CODE) STDIN.sync = true STDOUT.sync = true puts "hello" CODE else ChildProcess.build("echo", "hello") end end def ruby(code) ruby_process(tmp_script(code)) end def with_executable_at(path, &blk) if ChildProcess.os == :windows path << ".cmd" content = "#{RUBY} -e 'sleep 10' \n @echo foo" else content = "#!/bin/sh\nsleep 10\necho foo" end File.open(path, 'w', 0744) { |io| io << content } proc = ChildProcess.build(path) begin yield proc ensure proc.stop if proc.alive? File.delete path end end def exit_timeout 10 end def random_free_port server = TCPServer.new('127.0.0.1', 0) port = server.addr[1] server.close port end def with_tmpdir(&blk) name = "#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}" FileUtils.mkdir_p(name) begin yield File.expand_path(name) ensure FileUtils.rm_rf name end end def wait_until(timeout = 10, &blk) end_time = Time.now + timeout last_exception = nil until Time.now >= end_time begin result = yield return result if result rescue RSpec::Expectations::ExpectationNotMetError => ex last_exception = ex end sleep 0.01 end msg = "timed out after #{timeout} seconds" msg << ":\n#{last_exception.message}" if last_exception raise msg end def can_bind?(host, port) TCPServer.new(host, port).close true rescue false end def rewind_and_read(io) io.rewind io.read end def alive?(pid) !!Process.kill(0, pid) rescue Errno::ESRCH false end def capture_std orig_out = STDOUT.clone orig_err = STDERR.clone out = Tempfile.new 'captured-stdout' err = Tempfile.new 'captured-stderr' out.sync = true err.sync = true STDOUT.reopen out STDERR.reopen err yield CapturedOutput.new rewind_and_read(out), rewind_and_read(err) ensure STDOUT.reopen orig_out STDERR.reopen orig_err end def generate_log_messages ChildProcess.logger.level = Logger::DEBUG process = exit_with(0).start process.wait process.poll_for_exit(0.1) end end # ChildProcessSpecHelper Thread.abort_on_exception = true RSpec.configure do |c| c.include(ChildProcessSpecHelper) c.after(:each) { defined?(@process) && @process.alive? && @process.stop } end childprocess-5.1.0/spec/childprocess_spec.rb0000644000004100000410000002655514670202331021215 0ustar www-datawww-data# encoding: utf-8 require File.expand_path('../spec_helper', __FILE__) describe ChildProcess do here = File.dirname(__FILE__) let(:gemspec) { eval(File.read "#{here}/../childprocess.gemspec") } it 'validates cleanly' do if Gem::VERSION >= "3.5.0" expect { gemspec.validate }.not_to output(/warn/i).to_stderr else require 'rubygems/mock_gem_ui' mock_ui = Gem::MockGemUi.new Gem::DefaultUserInteraction.use_ui(mock_ui) { gemspec.validate } expect(mock_ui.error).to_not match(/warn/i) end end it "returns self when started" do process = sleeping_ruby expect(process.start).to eq process expect(process).to be_alive end it "raises ChildProcess::LaunchError if the process can't be started" do expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError) end it 'raises ArgumentError if given a non-string argument' do expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError) expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError) end it "knows if the process crashed" do process = exit_with(1).start process.wait expect(process).to be_crashed end it "knows if the process didn't crash" do process = exit_with(0).start process.wait expect(process).to_not be_crashed end it "can wait for a process to finish" do process = exit_with(0).start return_value = process.wait expect(process).to_not be_alive expect(return_value).to eq 0 end it 'ignores #wait if process already finished' do process = exit_with(0).start sleep 0.01 until process.exited? expect(process.wait).to eql 0 end it "escalates if TERM is ignored" do process = ignored('TERM').start process.stop expect(process).to be_exited end it "accepts a timeout argument to #stop" do process = sleeping_ruby.start process.stop(exit_timeout) end it "lets child process inherit the environment of the current process" do Tempfile.open("env-spec") do |file| file.close with_env('INHERITED' => 'yes') do process = write_env(file.path).start process.wait end file.open child_env = eval rewind_and_read(file) expect(child_env['INHERITED']).to eql 'yes' end end it "can override env vars only for the child process" do Tempfile.open("env-spec") do |file| file.close process = write_env(file.path) process.environment['CHILD_ONLY'] = '1' process.start expect(ENV['CHILD_ONLY']).to be_nil process.wait file.open child_env = eval rewind_and_read(file) expect(child_env['CHILD_ONLY']).to eql '1' end end it 'allows unicode characters in the environment' do Tempfile.open("env-spec") do |file| file.close process = write_env(file.path) process.environment['FOö'] = 'baör' process.start process.wait file.open child_env = eval rewind_and_read(file) expect(child_env['FOö']).to eql 'baör' end end it "can set env vars using Symbol keys and values" do Tempfile.open("env-spec") do |file| process = ruby('puts ENV["SYMBOL_KEY"]') process.environment[:SYMBOL_KEY] = :VALUE process.io.stdout = file process.start process.wait expect(rewind_and_read(file)).to eq "VALUE\n" end end it "raises ChildProcess::InvalidEnvironmentVariable for invalid env vars" do process = ruby(':OK') process.environment["a\0b"] = '1' expect { process.start }.to raise_error(ChildProcess::InvalidEnvironmentVariable) process = ruby(':OK') process.environment["A=1"] = '2' expect { process.start }.to raise_error(ChildProcess::InvalidEnvironmentVariable) process = ruby(':OK') process.environment['A'] = "a\0b" expect { process.start }.to raise_error(ChildProcess::InvalidEnvironmentVariable) end it "inherits the parent's env vars also when some are overridden" do Tempfile.open("env-spec") do |file| file.close with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do process = write_env(file.path) process.environment['CHILD_ONLY'] = 'yes' process.start process.wait file.open child_env = eval rewind_and_read(file) expect(child_env['INHERITED']).to eq 'yes' expect(child_env['CHILD_ONLY']).to eq 'yes' end end end it "can unset env vars" do Tempfile.open("env-spec") do |file| file.close ENV['CHILDPROCESS_UNSET'] = '1' process = write_env(file.path) process.environment['CHILDPROCESS_UNSET'] = nil process.start process.wait file.open child_env = eval rewind_and_read(file) expect(child_env).to_not have_key('CHILDPROCESS_UNSET') end end it 'does not see env vars unset in parent' do Tempfile.open('env-spec') do |file| file.close ENV['CHILDPROCESS_UNSET'] = nil process = write_env(file.path) process.start process.wait file.open child_env = eval rewind_and_read(file) expect(child_env).to_not have_key('CHILDPROCESS_UNSET') end end it "passes arguments to the child" do args = ["foo", "bar"] Tempfile.open("argv-spec") do |file| process = write_argv(file.path, *args).start process.wait expect(rewind_and_read(file)).to eql args.inspect end end it "lets a detached child live on" do p_pid = nil c_pid = nil Tempfile.open('grandparent_out') do |gp_file| # Create a parent and detached child process that will spit out their PID. Make sure that the child process lasts longer than the parent. p_process = ruby("$: << 'lib'; require 'childprocess' ; c_process = ChildProcess.build('ruby', '-e', 'puts \\\"Child PID: \#{Process.pid}\\\" ; sleep 5') ; c_process.io.inherit! ; c_process.detach = true ; c_process.start ; puts \"Child PID: \#{c_process.pid}\" ; puts \"Parent PID: \#{Process.pid}\"") p_process.io.stdout = p_process.io.stderr = gp_file # Let the parent process die p_process.start p_process.wait # Gather parent and child PIDs pids = rewind_and_read(gp_file).split("\n") pids.collect! { |pid| pid[/\d+/].to_i } c_pid, p_pid = pids end # Check that the parent process has dies but the child process is still alive expect(alive?(p_pid)).to_not be true expect(alive?(c_pid)).to be true end it "preserves Dir.pwd in the child" do Tempfile.open("dir-spec-out") do |file| process = ruby("print Dir.pwd") process.io.stdout = process.io.stderr = file expected_dir = nil Dir.chdir(Dir.tmpdir) do expected_dir = Dir.pwd process.start end process.wait expect(rewind_and_read(file)).to eq expected_dir end end it "can handle whitespace, special characters and quotes in arguments" do args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"'] Tempfile.open("argv-spec") do |file| process = write_argv(file.path, *args).start process.wait expect(rewind_and_read(file)).to eq args.inspect end end it 'handles whitespace in the executable name' do path = File.expand_path('foo bar') with_executable_at(path) do |proc| expect(proc.start).to eq proc expect(proc).to be_alive end end it "times out when polling for exit" do process = sleeping_ruby.start expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError) end it "can change working directory" do process = ruby "print Dir.pwd" with_tmpdir { |dir| process.cwd = dir orig_pwd = Dir.pwd Tempfile.open('cwd') do |file| process.io.stdout = file process.start process.wait expect(rewind_and_read(file)).to eq dir end expect(Dir.pwd).to eq orig_pwd } end it 'kills the full process tree' do Tempfile.open('kill-process-tree') do |file| process = write_pid_in_sleepy_grand_child(file.path) process.leader = true process.start pid = wait_until(30) do Integer(rewind_and_read(file)) rescue nil end process.stop expect(process).to be_exited wait_until(3) { expect(alive?(pid)).to eql(false) } end end it 'releases the GIL while waiting for the process' do time = Time.now threads = [] threads << Thread.new { sleeping_ruby(1).start.wait } threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 } threads.each { |t| t.join } end it 'can check if a detached child is alive' do proc = ruby_process("-e", "sleep") proc.detach = true proc.start expect(proc).to be_alive proc.stop(0) expect(proc).to be_exited end describe 'OS detection' do before(:all) do # Save off original OS so that it can be restored later @original_host_os = RbConfig::CONFIG['host_os'] end after(:each) do # Restore things to the real OS instead of the fake test OS RbConfig::CONFIG['host_os'] = @original_host_os ChildProcess.instance_variable_set(:@os, nil) end # TODO: add tests for other OSs context 'on a BSD system' do let(:bsd_patterns) { ['bsd', 'dragonfly'] } it 'correctly identifies BSD systems' do bsd_patterns.each do |pattern| RbConfig::CONFIG['host_os'] = pattern ChildProcess.instance_variable_set(:@os, nil) expect(ChildProcess.os).to eq(:bsd) end end end end it 'has a logger' do expect(ChildProcess).to respond_to(:logger) end it 'can change its logger' do expect(ChildProcess).to respond_to(:logger=) original_logger = ChildProcess.logger begin ChildProcess.logger = :some_other_logger expect(ChildProcess.logger).to eq(:some_other_logger) ensure ChildProcess.logger = original_logger end end describe 'logger' do before(:each) do ChildProcess.logger = logger end after(:all) do ChildProcess.logger = nil end context 'with the default logger' do let(:logger) { nil } it 'logs at INFO level by default' do expect(ChildProcess.logger.level).to eq(Logger::INFO) end it 'logs at DEBUG level by default if $DEBUG is on' do original_debug = $DEBUG begin $DEBUG = true expect(ChildProcess.logger.level).to eq(Logger::DEBUG) ensure $DEBUG = original_debug end end it "logs to stderr by default" do cap = capture_std { generate_log_messages } expect(cap.stdout).to be_empty expect(cap.stderr).to_not be_empty end end context 'with a custom logger' do let(:logger) { Logger.new($stdout) } it "logs to configured logger" do cap = capture_std { generate_log_messages } expect(cap.stdout).to_not be_empty expect(cap.stderr).to be_empty end end end describe '#started?' do subject { process.started? } context 'when not started' do let(:process) { sleeping_ruby(1) } it { is_expected.to be false } end context 'when started' do let(:process) { sleeping_ruby(1).start } it { is_expected.to be true } end context 'when finished' do before(:each) { process.wait } let(:process) { sleeping_ruby(0).start } it { is_expected.to be true } end end end childprocess-5.1.0/spec/abstract_io_spec.rb0000644000004100000410000000042414670202331021010 0ustar www-datawww-datarequire File.expand_path('../spec_helper', __FILE__) describe ChildProcess::AbstractIO do let(:io) { ChildProcess::AbstractIO.new } it "inherits the parent's IO streams" do io.inherit! expect(io.stdout).to eq STDOUT expect(io.stderr).to eq STDERR end end childprocess-5.1.0/spec/get_env.ps10000644000004100000410000000050214670202331017230 0ustar www-datawww-dataparam($p1) $env_list = Get-ChildItem Env: # Builds a ruby hash compatible string $hash_string = "{" foreach ($item in $env_list) { $hash_string += "`"" + $item.Name + "`" => `"" + $item.value.replace('\','\\').replace('"','\"') + "`"," } $hash_string += "}" $hash_string | out-File -Encoding "UTF8" $p1 childprocess-5.1.0/spec/io_spec.rb0000644000004100000410000001116414670202331017130 0ustar www-datawww-datarequire File.expand_path('../spec_helper', __FILE__) describe ChildProcess do it "can run even when $stdout is a StringIO" do begin stdout = $stdout $stdout = StringIO.new expect { sleeping_ruby.start }.to_not raise_error ensure $stdout = stdout end end it "can redirect stdout, stderr" do process = ruby(<<-CODE) [STDOUT, STDERR].each_with_index do |io, idx| io.sync = true io.puts idx end CODE out = Tempfile.new("stdout-spec") err = Tempfile.new("stderr-spec") begin process.io.stdout = out process.io.stderr = err process.start expect(process.io.stdin).to be_nil process.wait expect(rewind_and_read(out)).to eq "0\n" expect(rewind_and_read(err)).to eq "1\n" ensure out.close err.close end end it "can redirect stdout only" do process = ruby(<<-CODE) [STDOUT, STDERR].each_with_index do |io, idx| io.sync = true io.puts idx end CODE out = Tempfile.new("stdout-spec") begin process.io.stdout = out process.start process.wait expect(rewind_and_read(out)).to eq "0\n" ensure out.close end end it "pumps all output" do process = echo out = Tempfile.new("pump") begin process.io.stdout = out process.start process.wait expect(rewind_and_read(out)).to eq "hello\n" ensure out.close end end it "can write to stdin if duplex = true" do process = cat out = Tempfile.new("duplex") out.sync = true begin process.io.stdout = out process.io.stderr = out process.duplex = true process.start process.io.stdin.puts "hello world" process.io.stdin.close process.poll_for_exit(exit_timeout) expect(rewind_and_read(out)).to eq "hello world\n" ensure out.close end end it "can write to stdin interactively if duplex = true" do process = cat out = Tempfile.new("duplex") out.sync = true out_receiver = File.open(out.path, "rb") begin process.io.stdout = out process.io.stderr = out process.duplex = true process.start stdin = process.io.stdin stdin.puts "hello" stdin.flush wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\n\z/m) } stdin.putc "n" stdin.flush wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nn\z/m) } stdin.print "e" stdin.flush wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nne\z/m) } stdin.printf "w" stdin.flush wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nnew\z/m) } stdin.write "\nworld\n" stdin.flush wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nnew\r?\nworld\r?\n\z/m) } stdin.close process.poll_for_exit(exit_timeout) ensure out_receiver.close out.close end end # # this works on JRuby 1.6.5 on my Mac, but for some reason # hangs on Travis (running 1.6.5.1 + OpenJDK). # # http://travis-ci.org/#!/enkessler/childprocess/jobs/487331 # it "works with pipes" do process = ruby(<<-CODE) STDOUT.print "stdout" STDERR.print "stderr" CODE stdout, stdout_w = IO.pipe stderr, stderr_w = IO.pipe process.io.stdout = stdout_w process.io.stderr = stderr_w process.duplex = true process.start process.wait # write streams are closed *after* the process # has exited - otherwise it won't work on JRuby # with the current Process implementation stdout_w.close stderr_w.close out = stdout.read err = stderr.read expect([out, err]).to eq %w[stdout stderr] end it "can set close-on-exec when IO is inherited" do port = random_free_port server = TCPServer.new("127.0.0.1", port) ChildProcess.close_on_exec server process = sleeping_ruby process.io.inherit! process.start server.close wait_until { can_bind? "127.0.0.1", port } end it "handles long output" do process = ruby <<-CODE print 'a'*3000 CODE out = Tempfile.new("long-output") out.sync = true begin process.io.stdout = out process.start process.wait expect(rewind_and_read(out).size).to eq 3000 ensure out.close end end it 'should not inherit stdout and stderr by default' do cap = capture_std do process = echo process.start process.wait end expect(cap.stdout).to eq '' expect(cap.stderr).to eq '' end end childprocess-5.1.0/spec/unix_spec.rb0000644000004100000410000000331514670202331017503 0ustar www-datawww-datarequire File.expand_path('../spec_helper', __FILE__) require "pid_behavior" if ChildProcess.unix? describe ChildProcess::Unix::Process do it_behaves_like "a platform that provides the child's pid" it "handles ECHILD race condition where process dies between timeout and KILL" do process = sleeping_ruby allow(Process).to receive(:spawn).and_return('fakepid') allow(process).to receive(:send_term) allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError) allow(process).to receive(:send_kill).and_raise(Errno::ECHILD.new) process.start expect { process.stop }.not_to raise_error allow(process).to receive(:alive?).and_return(false) end it "handles ESRCH race condition where process dies between timeout and KILL" do process = sleeping_ruby allow(Process).to receive(:spawn).and_return('fakepid') allow(process).to receive(:send_term) allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError) allow(process).to receive(:send_kill).and_raise(Errno::ESRCH.new) process.start expect { process.stop }.not_to raise_error allow(process).to receive(:alive?).and_return(false) end end describe ChildProcess::Unix::IO do let(:io) { ChildProcess::Unix::IO.new } it "raises an ArgumentError if given IO does not respond to :to_io" do expect { io.stdout = nil }.to raise_error(ArgumentError, /to respond to :to_io/) end it "raises a TypeError if #to_io does not return an IO" do fake_io = Object.new def fake_io.to_io() StringIO.new end expect { io.stdout = fake_io }.to raise_error(TypeError, /expected IO, got/) end end end childprocess-5.1.0/spec/pid_behavior.rb0000644000004100000410000000052214670202331020136 0ustar www-datawww-datarequire File.expand_path('../spec_helper', __FILE__) shared_examples_for "a platform that provides the child's pid" do it "knows the child's pid" do Tempfile.open("pid-spec") do |file| process = write_pid(file.path).start process.wait expect(process.pid).to eq rewind_and_read(file).chomp.to_i end end end childprocess-5.1.0/spec/windows_spec.rb0000644000004100000410000000135014670202331020207 0ustar www-datawww-datarequire File.expand_path('../spec_helper', __FILE__) require "pid_behavior" if ChildProcess.windows? describe ChildProcess::Windows::Process do it_behaves_like "a platform that provides the child's pid" end describe ChildProcess::Windows::IO do let(:io) { ChildProcess::Windows::IO.new } it "raises an ArgumentError if given IO does not respond to :fileno" do expect { io.stdout = nil }.to raise_error(ArgumentError, /must have :fileno or :to_io/) end it "raises an ArgumentError if the #to_io does not return an IO " do fake_io = Object.new def fake_io.to_io() StringIO.new end expect { io.stdout = fake_io }.to raise_error(ArgumentError, /must have :fileno or :to_io/) end end end childprocess-5.1.0/spec/platform_detection_spec.rb0000644000004100000410000000550314670202331022403 0ustar www-datawww-datarequire File.expand_path('../spec_helper', __FILE__) # Q: Should platform detection concern be extracted from ChildProcess? describe ChildProcess do describe ".arch" do subject { described_class.arch } before(:each) { described_class.instance_variable_set(:@arch, nil) } after(:each) { described_class.instance_variable_set(:@arch, nil) } shared_examples 'expected_arch_for_host_cpu' do |host_cpu, expected_arch| context "when host_cpu is '#{host_cpu}'" do before :each do allow(RbConfig::CONFIG). to receive(:[]). with('host_cpu'). and_return(expected_arch) end it { is_expected.to eq expected_arch } end end # Normal cases: not macosx - depends only on host_cpu context "when os is *not* 'macosx'" do before :each do allow(described_class).to receive(:os).and_return(:not_macosx) end [ { host_cpu: 'i386', expected_arch: 'i386' }, { host_cpu: 'i486', expected_arch: 'i386' }, { host_cpu: 'i586', expected_arch: 'i386' }, { host_cpu: 'i686', expected_arch: 'i386' }, { host_cpu: 'amd64', expected_arch: 'x86_64' }, { host_cpu: 'x86_64', expected_arch: 'x86_64' }, { host_cpu: 'ppc', expected_arch: 'powerpc' }, { host_cpu: 'powerpc', expected_arch: 'powerpc' }, { host_cpu: 'unknown', expected_arch: 'unknown' }, ].each do |args| include_context 'expected_arch_for_host_cpu', args.values end end # Special cases: macosx - when host_cpu is i686, have to re-check context "when os is 'macosx'" do before :each do allow(described_class).to receive(:os).and_return(:macosx) end context "when host_cpu is 'i686' " do shared_examples 'expected_arch_on_macosx_i686' do |is_64, expected_arch| context "when Ruby is #{is_64 ? 64 : 32}-bit" do before :each do allow(described_class). to receive(:is_64_bit?). and_return(is_64) end include_context 'expected_arch_for_host_cpu', 'i686', expected_arch end end [ { is_64: true, expected_arch: 'x86_64' }, { is_64: false, expected_arch: 'i386' } ].each do |args| include_context 'expected_arch_on_macosx_i686', args.values end end [ { host_cpu: 'amd64', expected_arch: 'x86_64' }, { host_cpu: 'x86_64', expected_arch: 'x86_64' }, { host_cpu: 'ppc', expected_arch: 'powerpc' }, { host_cpu: 'powerpc', expected_arch: 'powerpc' }, { host_cpu: 'unknown', expected_arch: 'unknown' }, ].each do |args| include_context 'expected_arch_for_host_cpu', args.values end end end end childprocess-5.1.0/childprocess.gemspec0000644000004100000410000000216614670202331020261 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "childprocess/version" Gem::Specification.new do |s| s.name = "childprocess" s.version = ChildProcess::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Jari Bakken", "Eric Kessler", "Shane da Silva"] s.email = ["morrow748@gmail.com", "shane@dasilva.io"] s.homepage = "https://github.com/enkessler/childprocess" s.summary = %q{A simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.} s.description = %q{This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.} s.license = 'MIT' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- spec/*`.split("\n") s.require_paths = ["lib"] s.required_ruby_version = '>= 2.4.0' s.add_dependency "logger", "~> 1.5" s.add_development_dependency "rspec", "~> 3.0" s.add_development_dependency "yard", "~> 0.0" s.add_development_dependency 'coveralls', '< 1.0' end childprocess-5.1.0/.rspec0000644000004100000410000000001014670202331015331 0ustar www-datawww-data--color childprocess-5.1.0/Rakefile0000644000004100000410000000307214670202331015674 0ustar www-datawww-datarequire 'rubygems' require 'rake' require 'tmpdir' require 'bundler' Bundler::GemHelper.install_tasks include Rake::DSL if defined?(::Rake::DSL) require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.ruby_opts = "-I lib:spec -w" spec.pattern = 'spec/**/*_spec.rb' end desc 'Run specs for rcov' RSpec::Core::RakeTask.new(:rcov) do |spec| spec.ruby_opts = "-I lib:spec" spec.pattern = 'spec/**/*_spec.rb' spec.rcov = true spec.rcov_opts = %w[--exclude spec,ruby-debug,/Library/Ruby,.gem --include lib/childprocess] end task :default => :spec begin require 'yard' YARD::Rake::YardocTask.new rescue LoadError task :yardoc do abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard" end end task :clean do rm_rf "pkg" rm_rf "childprocess.jar" end desc 'Create jar to bundle in selenium-webdriver' task :jar => [:clean, :build] do tmpdir = Dir.mktmpdir("childprocess-jar") gem_to_package = Dir['pkg/*.gem'].first gem_name = File.basename(gem_to_package, ".gem") p :gem_to_package => gem_to_package, :gem_name => gem_name sh "gem install -i #{tmpdir} #{gem_to_package} --ignore-dependencies --no-rdoc --no-ri" sh "jar cf childprocess.jar -C #{tmpdir}/gems/#{gem_name}/lib ." sh "jar tf childprocess.jar" end task :env do $:.unshift File.expand_path("../lib", __FILE__) require 'childprocess' end desc 'Calculate size of posix_spawn structs for the current platform' task :generate => :env do require 'childprocess/tools/generator' ChildProcess::Tools::Generator.generate end childprocess-5.1.0/Gemfile0000644000004100000410000000050414670202331015517 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in child_process.gemspec gemspec # Used for local development/testing only gem 'rake' # Newer versions of term-ansicolor (used by coveralls) do not work on Ruby 2.4 gem 'term-ansicolor', '< 1.8.0' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5') childprocess-5.1.0/LICENSE0000644000004100000410000000204414670202331015232 0ustar www-datawww-dataCopyright (c) 2010-2015 Jari Bakken Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. childprocess-5.1.0/README.md0000644000004100000410000001472614670202331015516 0ustar www-datawww-data# childprocess This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination. The code originated in the [selenium-webdriver](https://rubygems.org/gems/selenium-webdriver) gem, but should prove useful as a standalone library. [![CI](https://github.com/enkessler/childprocess/actions/workflows/ci.yml/badge.svg)](https://github.com/enkessler/childprocess/actions/workflows/ci.yml) ![Gem Version](https://img.shields.io/gem/v/childprocess) [![Code Climate](https://codeclimate.com/github/enkessler/childprocess.svg)](https://codeclimate.com/github/enkessler/childprocess) [![Coverage Status](https://coveralls.io/repos/enkessler/childprocess/badge.svg?branch=master)](https://coveralls.io/r/enkessler/childprocess?branch=master) # Requirements * Ruby 2.4+, JRuby 9+ # Usage The object returned from `ChildProcess.build` will implement `ChildProcess::AbstractProcess`. ### Basic examples ```ruby process = ChildProcess.build("ruby", "-e", "sleep") # inherit stdout/stderr from parent... process.io.inherit! # ...or pass an IO process.io.stdout = Tempfile.new("child-output") # modify the environment for the child process.environment["a"] = "b" process.environment["c"] = nil # set the child's working directory process.cwd = '/some/path' # start the process process.start # check process status process.alive? #=> true process.exited? #=> false # wait indefinitely for process to exit... process.wait process.exited? #=> true # get the exit code process.exit_code #=> 0 # ...or poll for exit + force quit begin process.poll_for_exit(10) rescue ChildProcess::TimeoutError process.stop # tries increasingly harsher methods to kill the process. end ``` ### Advanced examples #### Output to pipe ```ruby r, w = IO.pipe begin process = ChildProcess.build("sh" , "-c", "for i in {1..3}; do echo $i; sleep 1; done") process.io.stdout = w process.start # This results in a subprocess inheriting the write end of the pipe. # Close parent's copy of the write end of the pipe so when the child # process closes its write end of the pipe the parent receives EOF when # attempting to read from it. If the parent leaves its write end open, it # will not detect EOF. w.close thread = Thread.new do begin loop do print r.readpartial(16384) end rescue EOFError # Child has closed the write end of the pipe end end process.wait thread.join ensure r.close end ``` Note that if you just want to get the output of a command, the backtick method on Kernel may be a better fit. #### Write to stdin ```ruby process = ChildProcess.build("cat") out = Tempfile.new("duplex") out.sync = true process.io.stdout = process.io.stderr = out process.duplex = true # sets up pipe so process.io.stdin will be available after .start process.start process.io.stdin.puts "hello world" process.io.stdin.close process.poll_for_exit(exit_timeout_in_seconds) out.rewind out.read #=> "hello world\n" ``` #### Pipe output to another ChildProcess ```ruby search = ChildProcess.build("grep", '-E', %w(redis memcached).join('|')) search.duplex = true # sets up pipe so search.io.stdin will be available after .start search.io.stdout = $stdout search.start listing = ChildProcess.build("ps", "aux") listing.io.stdout = search.io.stdin listing.start listing.wait search.io.stdin.close search.wait ``` ### Ensure entire process tree dies By default, the child process does not create a new process group. This means there's no guarantee that the entire process tree will die when the child process is killed. To solve this: ```ruby process = ChildProcess.build(*args) process.leader = true process.start ``` #### Detach from parent ```ruby process = ChildProcess.build("sleep", "10") process.detach = true process.start ``` #### Invoking a shell As opposed to `Kernel#system`, `Kernel#exec` et al., ChildProcess will not automatically execute your command in a shell (like `/bin/sh` or `cmd.exe`) depending on the arguments. This means that if you try to execute e.g. gem executables (like `bundle` or `gem`) or Windows executables (with `.com` or `.bat` extensions) you may see a `ChildProcess::LaunchError`. You can work around this by being explicit about what interpreter to invoke: ```ruby ChildProcess.build("cmd.exe", "/c", "bundle") ChildProcess.build("ruby", "-S", "bundle") ``` #### Log to file Errors and debugging information are logged to `$stderr` by default but a custom logger can be used instead. ```ruby logger = Logger.new('logfile.log') logger.level = Logger::DEBUG ChildProcess.logger = logger ``` ## Caveats * With JRuby on Unix, modifying `ENV["PATH"]` before using childprocess could lead to 'Command not found' errors, since JRuby is unable to modify the environment used for PATH searches in `java.lang.ProcessBuilder`. This can be avoided by setting `ChildProcess.posix_spawn = true`. * With JRuby on Java >= 9, the JVM may need to be configured to allow JRuby to access neccessary implementations; this can be done by adding `--add-opens java.base/java.io=org.jruby.dist` and `--add-opens java.base/sun.nio.ch=org.jruby.dist` to the `JAVA_OPTS` environment variable that is used by JRuby when launching the JVM. # Implementation ChildProcess 5+ uses `Process.spawn` from the Ruby core library for maximum portability. # Note on Patches/Pull Requests 1. Fork it 2. Create your feature branch (off of the development branch) `git checkout -b my-new-feature dev` 3. Commit your changes `git commit -am 'Add some feature'` 4. Push to the branch `git push origin my-new-feature` 5. Create new Pull Request # Publishing a New Release When publishing a new gem release: 1. Ensure [latest build is green on the `dev` branch](https://travis-ci.org/enkessler/childprocess/branches) 2. Ensure [CHANGELOG](CHANGELOG.md) is updated 3. Ensure [version is bumped](lib/childprocess/version.rb) following [Semantic Versioning](https://semver.org/) 4. Merge the `dev` branch into `master`: `git checkout master && git merge dev` 5. Ensure [latest build is green on the `master` branch](https://travis-ci.org/enkessler/childprocess/branches) 6. Build gem from the green `master` branch: `git checkout master && gem build childprocess.gemspec` 7. Push gem to RubyGems: `gem push childprocess-.gem` 8. Tag commit with version, annotated with release notes: `git tag -a ` # Copyright Copyright (c) 2010-2015 Jari Bakken. See [LICENSE](LICENSE) for details. childprocess-5.1.0/CHANGELOG.md0000644000004100000410000000672614670202331016051 0ustar www-datawww-data### Version 5.1.0 / 2024-01-06 * [#196](https://github.com/enkessler/childprocess/pull/196): Remove `ostruct` dependency to fix deprecation warning on Ruby 3.4 * [#199](https://github.com/enkessler/childprocess/pull/199): Add `logger` dependency to fix deprecation warning on Ruby 3.4 ### Version 5.0.0 / 2024-01-06 * [#175](https://github.com/enkessler/childprocess/pull/175): Replace all backends by `Process.spawn` for portability, reliability and simplicity. * [#185](https://github.com/enkessler/childprocess/pull/185): Add support for Ruby 3.x ### Version 4.1.0 / 2021-06-08 * [#170](https://github.com/enkessler/childprocess/pull/170): Update gem homepage to use `https://` * [#177](https://github.com/enkessler/childprocess/pull/177): Add ARM64-macos support ### Version 4.0.0 / 2020-06-18 * [#167](https://github.com/enkessler/childprocess/pull/167): Fix detach behavior on Windows * [#168](https://github.com/enkessler/childprocess/pull/168): Drop support for Ruby 2.3 ### Version 3.0.0 / 2019-09-20 * [#156](https://github.com/enkessler/childprocess/pull/156): Remove unused `rubyforge_project` from gemspec * [#160](https://github.com/enkessler/childprocess/pull/160): Remove extension to conditionally install `ffi` gem on Windows platforms * [#160](https://github.com/enkessler/childprocess/pull/160): Remove runtime dependency on `rake` gem ### Version 2.0.0 / 2019-07-11 * [#148](https://github.com/enkessler/childprocess/pull/148): Drop support for Ruby 2.0, 2.1, and 2.2 * [#149](https://github.com/enkessler/childprocess/pull/149): Fix Unix fork reopen to be compatible with Ruby 2.6 * [#152](https://github.com/enkessler/childprocess/pull/152)/[#154](https://github.com/enkessler/childprocess/pull/154): Fix hangs and permission errors introduced in Ruby 2.6 for leader processes of process groups ### Version 1.0.1 / 2019-02-03 * [#143](https://github.com/enkessler/childprocess/pull/144): Fix installs by adding `rake` gem as runtime dependency * [#147](https://github.com/enkessler/childprocess/pull/147): Relax `rake` gem constraint from `< 12` to `< 13` ### Version 1.0.0 / 2019-01-28 * [#134](https://github.com/enkessler/childprocess/pull/134): Add support for non-ASCII characters on Windows * [#132](https://github.com/enkessler/childprocess/pull/132): Install `ffi` gem requirement on Windows only * [#128](https://github.com/enkessler/childprocess/issues/128): Convert environment variable values to strings when `posix_spawn` enabled * [#141](https://github.com/enkessler/childprocess/pull/141): Support JRuby on Java >= 9 ### Version 0.9.0 / 2018-03-10 * Added support for DragonFly BSD. ### Version 0.8.0 / 2017-09-23 * Added a method for determining whether or not a process had been started. ### Version 0.7.1 / 2017-06-26 * Fixed a noisy uninitialized variable warning ### Version 0.7.0 / 2017-05-07 * Debugging information now uses a Logger, which can be configured. ### Version 0.6.3 / 2017-03-24 See beta release notes. ### Version 0.6.3.beta.1 / 2017-03-10 * Bug fix: Fixed child process creation problems on Windows 7 when a child was declared as a leader. ### Version 0.6.2 / 2017-02-25 * Bug fix: Fixed a potentially broken edge case that could occur on older 32-bit OSX systems. ### Version 0.6.1 / 2017-01-22 * Bug fix: Fixed a dependency that was accidentally declared as a runtime dependency instead of a development dependency. ### Version 0.6.0 / 2017-01-22 * Support for Ruby 2.4 added ### Version 0.5.9 / 2016-01-06 * The Great Before Times...