childprocess 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/.gitignore +28 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +36 -0
  6. data/CHANGELOG.md +44 -0
  7. data/Gemfile +15 -0
  8. data/LICENSE +20 -0
  9. data/README.md +192 -0
  10. data/Rakefile +61 -0
  11. data/appveyor.yml +43 -0
  12. data/childprocess.gemspec +30 -0
  13. data/lib/childprocess.rb +205 -0
  14. data/lib/childprocess/abstract_io.rb +36 -0
  15. data/lib/childprocess/abstract_process.rb +192 -0
  16. data/lib/childprocess/errors.rb +26 -0
  17. data/lib/childprocess/jruby.rb +56 -0
  18. data/lib/childprocess/jruby/io.rb +16 -0
  19. data/lib/childprocess/jruby/process.rb +159 -0
  20. data/lib/childprocess/jruby/pump.rb +53 -0
  21. data/lib/childprocess/tools/generator.rb +146 -0
  22. data/lib/childprocess/unix.rb +9 -0
  23. data/lib/childprocess/unix/fork_exec_process.rb +70 -0
  24. data/lib/childprocess/unix/io.rb +21 -0
  25. data/lib/childprocess/unix/lib.rb +186 -0
  26. data/lib/childprocess/unix/platform/i386-linux.rb +12 -0
  27. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -0
  28. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -0
  29. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -0
  30. data/lib/childprocess/unix/posix_spawn_process.rb +134 -0
  31. data/lib/childprocess/unix/process.rb +89 -0
  32. data/lib/childprocess/version.rb +3 -0
  33. data/lib/childprocess/windows.rb +33 -0
  34. data/lib/childprocess/windows/handle.rb +91 -0
  35. data/lib/childprocess/windows/io.rb +25 -0
  36. data/lib/childprocess/windows/lib.rb +416 -0
  37. data/lib/childprocess/windows/process.rb +130 -0
  38. data/lib/childprocess/windows/process_builder.rb +175 -0
  39. data/lib/childprocess/windows/structs.rb +149 -0
  40. data/spec/abstract_io_spec.rb +12 -0
  41. data/spec/childprocess_spec.rb +391 -0
  42. data/spec/io_spec.rb +228 -0
  43. data/spec/jruby_spec.rb +24 -0
  44. data/spec/pid_behavior.rb +12 -0
  45. data/spec/platform_detection_spec.rb +86 -0
  46. data/spec/spec_helper.rb +261 -0
  47. data/spec/unix_spec.rb +57 -0
  48. data/spec/windows_spec.rb +23 -0
  49. metadata +179 -0
@@ -0,0 +1,26 @@
1
+ module ChildProcess
2
+ class Error < StandardError
3
+ end
4
+
5
+ class TimeoutError < Error
6
+ end
7
+
8
+ class SubclassResponsibility < Error
9
+ end
10
+
11
+ class InvalidEnvironmentVariable < Error
12
+ end
13
+
14
+ class LaunchError < Error
15
+ end
16
+
17
+ class MissingPlatformError < Error
18
+ def initialize
19
+ message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to default implementation. " +
20
+ "If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues"
21
+
22
+ super(message)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ require 'java'
2
+ require 'jruby'
3
+
4
+ class Java::SunNioCh::FileChannelImpl
5
+ field_reader :fd
6
+ end
7
+
8
+ class Java::JavaIo::FileDescriptor
9
+ if ChildProcess.os == :windows
10
+ field_reader :handle
11
+ end
12
+
13
+ field_reader :fd
14
+ end
15
+
16
+ module ChildProcess
17
+ module JRuby
18
+ def self.posix_fileno_for(obj)
19
+ channel = ::JRuby.reference(obj).channel
20
+ begin
21
+ channel.getFDVal
22
+ rescue NoMethodError
23
+ fileno = channel.fd
24
+ if fileno.kind_of?(Java::JavaIo::FileDescriptor)
25
+ fileno = fileno.fd
26
+ end
27
+
28
+ fileno == -1 ? obj.fileno : fileno
29
+ end
30
+ rescue
31
+ # fall back
32
+ obj.fileno
33
+ end
34
+
35
+ def self.windows_handle_for(obj)
36
+ channel = ::JRuby.reference(obj).channel
37
+ fileno = obj.fileno
38
+
39
+ begin
40
+ fileno = channel.getFDVal
41
+ rescue NoMethodError
42
+ fileno = channel.fd if channel.respond_to?(:fd)
43
+ end
44
+
45
+ if fileno.kind_of? Java::JavaIo::FileDescriptor
46
+ fileno.handle
47
+ else
48
+ Windows::Lib.handle_for fileno
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ require "childprocess/jruby/pump"
55
+ require "childprocess/jruby/io"
56
+ require "childprocess/jruby/process"
@@ -0,0 +1,16 @@
1
+ module ChildProcess
2
+ module JRuby
3
+ class IO < AbstractIO
4
+ private
5
+
6
+ def check_type(output)
7
+ unless output.respond_to?(:to_outputstream) && output.respond_to?(:write)
8
+ raise ArgumentError, "expected #{output.inspect} to respond to :to_outputstream"
9
+ end
10
+ end
11
+
12
+ end # IO
13
+ end # Unix
14
+ end # ChildProcess
15
+
16
+
@@ -0,0 +1,159 @@
1
+ require "java"
2
+
3
+ module ChildProcess
4
+ module JRuby
5
+ class Process < AbstractProcess
6
+ def initialize(args)
7
+ super(args)
8
+
9
+ @pumps = []
10
+ end
11
+
12
+ def io
13
+ @io ||= JRuby::IO.new
14
+ end
15
+
16
+ def exited?
17
+ return true if @exit_code
18
+
19
+ assert_started
20
+ @exit_code = @process.exitValue
21
+ stop_pumps
22
+
23
+ true
24
+ rescue java.lang.IllegalThreadStateException => ex
25
+ log(ex.class => ex.message)
26
+ false
27
+ ensure
28
+ log(:exit_code => @exit_code)
29
+ end
30
+
31
+ def stop(timeout = nil)
32
+ assert_started
33
+
34
+ @process.destroy
35
+ wait # no way to actually use the timeout here..
36
+ end
37
+
38
+ def wait
39
+ if exited?
40
+ exit_code
41
+ else
42
+ @process.waitFor
43
+
44
+ stop_pumps
45
+ @exit_code = @process.exitValue
46
+ end
47
+ end
48
+
49
+ #
50
+ # Only supported in JRuby on a Unix operating system, thanks to limitations
51
+ # in Java's classes
52
+ #
53
+ # @return [Integer] the pid of the process after it has started
54
+ # @raise [NotImplementedError] when trying to access pid on non-Unix platform
55
+ #
56
+ def pid
57
+ if @process.getClass.getName != "java.lang.UNIXProcess"
58
+ raise NotImplementedError, "pid is only supported by JRuby child processes on Unix"
59
+ end
60
+
61
+ # About the best way we can do this is with a nasty reflection-based impl
62
+ # Thanks to Martijn Courteaux
63
+ # http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigter/2951193#2951193
64
+ field = @process.getClass.getDeclaredField("pid")
65
+ field.accessible = true
66
+ field.get(@process)
67
+ end
68
+
69
+ private
70
+
71
+ def launch_process(&blk)
72
+ pb = java.lang.ProcessBuilder.new(@args)
73
+
74
+ pb.directory java.io.File.new(@cwd || Dir.pwd)
75
+ set_env pb.environment
76
+
77
+ begin
78
+ @process = pb.start
79
+ rescue java.io.IOException => ex
80
+ raise LaunchError, ex.message
81
+ end
82
+
83
+ setup_io
84
+ end
85
+
86
+ def setup_io
87
+ if @io
88
+ redirect(@process.getErrorStream, @io.stderr)
89
+ redirect(@process.getInputStream, @io.stdout)
90
+ else
91
+ @process.getErrorStream.close
92
+ @process.getInputStream.close
93
+ end
94
+
95
+ if duplex?
96
+ io._stdin = create_stdin
97
+ else
98
+ @process.getOutputStream.close
99
+ end
100
+ end
101
+
102
+ def redirect(input, output)
103
+ if output.nil?
104
+ input.close
105
+ return
106
+ end
107
+
108
+ @pumps << Pump.new(input, output.to_outputstream).run
109
+ end
110
+
111
+ def stop_pumps
112
+ @pumps.each { |pump| pump.stop }
113
+ end
114
+
115
+ def set_env(env)
116
+ merged = ENV.to_hash
117
+
118
+ @environment.each { |k, v| merged[k.to_s] = v }
119
+
120
+ merged.each do |k, v|
121
+ if v
122
+ env.put(k, v.to_s)
123
+ elsif env.has_key? k
124
+ env.remove(k)
125
+ end
126
+ end
127
+
128
+ removed_keys = env.key_set.to_a - merged.keys
129
+ removed_keys.each { |k| env.remove(k) }
130
+ end
131
+
132
+ def create_stdin
133
+ output_stream = @process.getOutputStream
134
+
135
+ stdin = output_stream.to_io
136
+ stdin.sync = true
137
+ stdin.instance_variable_set(:@childprocess_java_stream, output_stream)
138
+
139
+ class << stdin
140
+ # The stream provided is a BufferedeOutputStream, so we
141
+ # have to flush it to make the bytes flow to the process
142
+ def __childprocess_flush__
143
+ @childprocess_java_stream.flush
144
+ end
145
+
146
+ [:flush, :print, :printf, :putc, :puts, :write, :write_nonblock].each do |m|
147
+ define_method(m) do |*args|
148
+ super(*args)
149
+ self.__childprocess_flush__
150
+ end
151
+ end
152
+ end
153
+
154
+ stdin
155
+ end
156
+
157
+ end # Process
158
+ end # JRuby
159
+ end # ChildProcess
@@ -0,0 +1,53 @@
1
+ module ChildProcess
2
+ module JRuby
3
+ class Pump
4
+ BUFFER_SIZE = 2048
5
+
6
+ def initialize(input, output)
7
+ @input = input
8
+ @output = output
9
+ @stop = false
10
+ end
11
+
12
+ def stop
13
+ @stop = true
14
+ @thread && @thread.join
15
+ end
16
+
17
+ def run
18
+ @thread = Thread.new { pump }
19
+
20
+ self
21
+ end
22
+
23
+ private
24
+
25
+ def pump
26
+ buffer = Java.byte[BUFFER_SIZE].new
27
+
28
+ until @stop && (@input.available == 0)
29
+ read, avail = 0, 0
30
+
31
+ while read != -1
32
+ avail = [@input.available, 1].max
33
+ avail = BUFFER_SIZE if avail > BUFFER_SIZE
34
+ read = @input.read(buffer, 0, avail)
35
+
36
+ if read > 0
37
+ @output.write(buffer, 0, read)
38
+ @output.flush
39
+ end
40
+ end
41
+
42
+ sleep 0.1
43
+ end
44
+
45
+ @output.flush
46
+ rescue java.io.IOException => ex
47
+ ChildProcess.logger.debug ex.message
48
+ ChildProcess.logger.debug ex.backtrace
49
+ end
50
+
51
+ end # Pump
52
+ end # JRuby
53
+ end # ChildProcess
@@ -0,0 +1,146 @@
1
+ require 'fileutils'
2
+
3
+ module ChildProcess
4
+ module Tools
5
+ class Generator
6
+ EXE_NAME = "childprocess-sizeof-generator"
7
+ TMP_PROGRAM = "childprocess-sizeof-generator.c"
8
+ DEFAULT_INCLUDES = %w[stdio.h stddef.h]
9
+
10
+ def self.generate
11
+ new.generate
12
+ end
13
+
14
+ def initialize
15
+ @cc = ENV['CC'] || 'gcc'
16
+ @out = File.expand_path("../../unix/platform/#{ChildProcess.platform_name}.rb", __FILE__)
17
+ @sizeof = {}
18
+ @constants = {}
19
+ end
20
+
21
+ def generate
22
+ fetch_size 'posix_spawn_file_actions_t', :include => "spawn.h"
23
+ fetch_size 'posix_spawnattr_t', :include => "spawn.h"
24
+ fetch_size 'sigset_t', :include => "signal.h"
25
+
26
+ fetch_constant 'POSIX_SPAWN_RESETIDS', :include => 'spawn.h'
27
+ fetch_constant 'POSIX_SPAWN_SETPGROUP', :include => 'spawn.h'
28
+ fetch_constant 'POSIX_SPAWN_SETSIGDEF', :include => 'spawn.h'
29
+ fetch_constant 'POSIX_SPAWN_SETSIGMASK', :include => 'spawn.h'
30
+
31
+ if ChildProcess.linux?
32
+ fetch_constant 'POSIX_SPAWN_USEVFORK', :include => 'spawn.h', :define => {'_GNU_SOURCE' => nil}
33
+ end
34
+
35
+ write
36
+ end
37
+
38
+ def write
39
+ FileUtils.mkdir_p(File.dirname(@out))
40
+ File.open(@out, 'w') do |io|
41
+ io.puts result
42
+ end
43
+
44
+ puts "wrote #{@out}"
45
+ end
46
+
47
+ def fetch_size(type_name, opts = {})
48
+ print "sizeof(#{type_name}): "
49
+ src = <<-EOF
50
+ int main() {
51
+ printf("%d", (unsigned int)sizeof(#{type_name}));
52
+ return 0;
53
+ }
54
+ EOF
55
+
56
+ output = execute(src, opts)
57
+
58
+ if output.to_i < 1
59
+ raise "sizeof(#{type_name}) == #{output.to_i} (output=#{output})"
60
+ end
61
+
62
+ size = output.to_i
63
+ @sizeof[type_name] = size
64
+
65
+ puts size
66
+ end
67
+
68
+ def fetch_constant(name, opts)
69
+ print "#{name}: "
70
+ src = <<-EOF
71
+ int main() {
72
+ printf("%d", (unsigned int)#{name});
73
+ return 0;
74
+ }
75
+ EOF
76
+
77
+ output = execute(src, opts)
78
+ value = Integer(output)
79
+ @constants[name] = value
80
+
81
+ puts value
82
+ end
83
+
84
+
85
+ def execute(src, opts)
86
+ program = Array(opts[:define]).map do |key, value|
87
+ <<-SRC
88
+ #ifndef #{key}
89
+ #define #{key} #{value}
90
+ #endif
91
+ SRC
92
+ end.join("\n")
93
+ program << "\n"
94
+
95
+ includes = Array(opts[:include]) + DEFAULT_INCLUDES
96
+ program << includes.map { |include| "#include <#{include}>" }.join("\n")
97
+ program << "\n#{src}"
98
+
99
+ File.open(TMP_PROGRAM, 'w') do |file|
100
+ file << program
101
+ end
102
+
103
+ cmd = "#{@cc} #{TMP_PROGRAM} -o #{EXE_NAME}"
104
+ system cmd
105
+ unless $?.success?
106
+ raise "failed to compile program: #{cmd.inspect}\n#{program}"
107
+ end
108
+
109
+ output = `./#{EXE_NAME} 2>&1`
110
+
111
+ unless $?.success?
112
+ raise "failed to run program: #{cmd.inspect}\n#{output}"
113
+ end
114
+
115
+ output.chomp
116
+ ensure
117
+ File.delete TMP_PROGRAM if File.exist?(TMP_PROGRAM)
118
+ File.delete EXE_NAME if File.exist?(EXE_NAME)
119
+ end
120
+
121
+ def result
122
+ if @sizeof.empty? && @constants.empty?
123
+ raise "no data collected, nothing to do"
124
+ end
125
+
126
+ out = ['module ChildProcess::Unix::Platform']
127
+ out << ' SIZEOF = {'
128
+
129
+ max = @sizeof.keys.map { |e| e.length }.max
130
+ @sizeof.each_with_index do |(type, size), idx|
131
+ out << " :#{type.ljust max} => #{size}#{',' unless idx == @sizeof.size - 1}"
132
+ end
133
+ out << ' }'
134
+
135
+ max = @constants.keys.map { |e| e.length }.max
136
+ @constants.each do |name, val|
137
+ out << " #{name.ljust max} = #{val}"
138
+ end
139
+ out << 'end'
140
+
141
+ out.join "\n"
142
+ end
143
+
144
+ end
145
+ end
146
+ end