childprocess 0.8.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.document +6 -6
  3. data/.gitignore +28 -28
  4. data/.rspec +1 -1
  5. data/.travis.yml +40 -36
  6. data/CHANGELOG.md +73 -44
  7. data/Gemfile +21 -15
  8. data/LICENSE +20 -20
  9. data/README.md +218 -192
  10. data/Rakefile +61 -61
  11. data/appveyor.yml +42 -43
  12. data/childprocess.gemspec +26 -30
  13. data/lib/childprocess.rb +210 -205
  14. data/lib/childprocess/abstract_io.rb +36 -36
  15. data/lib/childprocess/abstract_process.rb +192 -192
  16. data/lib/childprocess/errors.rb +37 -26
  17. data/lib/childprocess/jruby.rb +56 -56
  18. data/lib/childprocess/jruby/io.rb +16 -16
  19. data/lib/childprocess/jruby/process.rb +184 -159
  20. data/lib/childprocess/jruby/pump.rb +53 -53
  21. data/lib/childprocess/tools/generator.rb +145 -145
  22. data/lib/childprocess/unix.rb +9 -9
  23. data/lib/childprocess/unix/fork_exec_process.rb +78 -70
  24. data/lib/childprocess/unix/io.rb +21 -21
  25. data/lib/childprocess/unix/lib.rb +186 -186
  26. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  27. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  28. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  29. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  30. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  31. data/lib/childprocess/unix/process.rb +90 -89
  32. data/lib/childprocess/version.rb +3 -3
  33. data/lib/childprocess/windows.rb +38 -33
  34. data/lib/childprocess/windows/handle.rb +91 -91
  35. data/lib/childprocess/windows/io.rb +25 -25
  36. data/lib/childprocess/windows/lib.rb +416 -416
  37. data/lib/childprocess/windows/process.rb +130 -130
  38. data/lib/childprocess/windows/process_builder.rb +178 -175
  39. data/lib/childprocess/windows/structs.rb +148 -148
  40. data/spec/abstract_io_spec.rb +12 -12
  41. data/spec/childprocess_spec.rb +447 -391
  42. data/spec/get_env.ps1 +13 -0
  43. data/spec/io_spec.rb +228 -228
  44. data/spec/jruby_spec.rb +24 -24
  45. data/spec/pid_behavior.rb +12 -12
  46. data/spec/platform_detection_spec.rb +86 -86
  47. data/spec/spec_helper.rb +270 -261
  48. data/spec/unix_spec.rb +57 -57
  49. data/spec/windows_spec.rb +23 -23
  50. metadata +8 -39
@@ -1,26 +1,37 @@
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
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 MissingFFIError < Error
18
+ def initialize
19
+ message = "FFI is a required pre-requisite for Windows or posix_spawn support in the ChildProcess gem. " +
20
+ "Ensure the `ffi` gem is installed. " +
21
+ "If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues"
22
+
23
+ super(message)
24
+ end
25
+
26
+ end
27
+
28
+ class MissingPlatformError < Error
29
+ def initialize
30
+ message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to default implementation. " +
31
+ "If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues"
32
+
33
+ super(message)
34
+ end
35
+
36
+ end
37
+ end
@@ -1,56 +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"
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"
@@ -1,16 +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
-
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
+
@@ -1,159 +1,184 @@
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
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
+ # Implementation of ChildProcess::JRuby::Process#pid depends heavily on
50
+ # what Java SDK is being used; here, we look it up once at load, then
51
+ # define the method once to avoid runtime overhead.
52
+ normalised_java_version_major = java.lang.System.get_property("java.version")
53
+ .slice(/^(1\.)?([0-9]+)/, 2)
54
+ .to_i
55
+ if normalised_java_version_major >= 9
56
+
57
+ # On modern Javas, we can simply delegate through to `Process#pid`,
58
+ # which was introduced in Java 9.
59
+ #
60
+ # @return [Integer] the pid of the process after it has started
61
+ # @raise [NotImplementedError] when trying to access pid on platform for
62
+ # which it is unsupported in Java
63
+ def pid
64
+ @process.pid
65
+ rescue java.lang.UnsupportedOperationException => e
66
+ raise NotImplementedError, "pid is not supported on this platform: #{e.message}"
67
+ end
68
+
69
+ else
70
+
71
+ # On Legacy Javas, fall back to reflection.
72
+ #
73
+ # Only supported in JRuby on a Unix operating system, thanks to limitations
74
+ # in Java's classes
75
+ #
76
+ # @return [Integer] the pid of the process after it has started
77
+ # @raise [NotImplementedError] when trying to access pid on non-Unix platform
78
+ #
79
+ def pid
80
+ if @process.getClass.getName != "java.lang.UNIXProcess"
81
+ raise NotImplementedError, "pid is only supported by JRuby child processes on Unix"
82
+ end
83
+
84
+ # About the best way we can do this is with a nasty reflection-based impl
85
+ # Thanks to Martijn Courteaux
86
+ # http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigter/2951193#2951193
87
+ field = @process.getClass.getDeclaredField("pid")
88
+ field.accessible = true
89
+ field.get(@process)
90
+ end
91
+
92
+ end
93
+
94
+ private
95
+
96
+ def launch_process(&blk)
97
+ pb = java.lang.ProcessBuilder.new(@args)
98
+
99
+ pb.directory java.io.File.new(@cwd || Dir.pwd)
100
+ set_env pb.environment
101
+
102
+ begin
103
+ @process = pb.start
104
+ rescue java.io.IOException => ex
105
+ raise LaunchError, ex.message
106
+ end
107
+
108
+ setup_io
109
+ end
110
+
111
+ def setup_io
112
+ if @io
113
+ redirect(@process.getErrorStream, @io.stderr)
114
+ redirect(@process.getInputStream, @io.stdout)
115
+ else
116
+ @process.getErrorStream.close
117
+ @process.getInputStream.close
118
+ end
119
+
120
+ if duplex?
121
+ io._stdin = create_stdin
122
+ else
123
+ @process.getOutputStream.close
124
+ end
125
+ end
126
+
127
+ def redirect(input, output)
128
+ if output.nil?
129
+ input.close
130
+ return
131
+ end
132
+
133
+ @pumps << Pump.new(input, output.to_outputstream).run
134
+ end
135
+
136
+ def stop_pumps
137
+ @pumps.each { |pump| pump.stop }
138
+ end
139
+
140
+ def set_env(env)
141
+ merged = ENV.to_hash
142
+
143
+ @environment.each { |k, v| merged[k.to_s] = v }
144
+
145
+ merged.each do |k, v|
146
+ if v
147
+ env.put(k, v.to_s)
148
+ elsif env.has_key? k
149
+ env.remove(k)
150
+ end
151
+ end
152
+
153
+ removed_keys = env.key_set.to_a - merged.keys
154
+ removed_keys.each { |k| env.remove(k) }
155
+ end
156
+
157
+ def create_stdin
158
+ output_stream = @process.getOutputStream
159
+
160
+ stdin = output_stream.to_io
161
+ stdin.sync = true
162
+ stdin.instance_variable_set(:@childprocess_java_stream, output_stream)
163
+
164
+ class << stdin
165
+ # The stream provided is a BufferedeOutputStream, so we
166
+ # have to flush it to make the bytes flow to the process
167
+ def __childprocess_flush__
168
+ @childprocess_java_stream.flush
169
+ end
170
+
171
+ [:flush, :print, :printf, :putc, :puts, :write, :write_nonblock].each do |m|
172
+ define_method(m) do |*args|
173
+ super(*args)
174
+ self.__childprocess_flush__
175
+ end
176
+ end
177
+ end
178
+
179
+ stdin
180
+ end
181
+
182
+ end # Process
183
+ end # JRuby
184
+ end # ChildProcess