childprocess 1.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.document +6 -6
  3. data/.gitignore +28 -28
  4. data/.rspec +1 -1
  5. data/.travis.yml +37 -52
  6. data/CHANGELOG.md +83 -56
  7. data/Gemfile +9 -18
  8. data/LICENSE +20 -20
  9. data/README.md +230 -200
  10. data/Rakefile +61 -61
  11. data/appveyor.yml +36 -60
  12. data/childprocess.gemspec +26 -29
  13. data/lib/childprocess.rb +210 -210
  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 -37
  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 -184
  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/arm64-macosx.rb +11 -0
  27. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  28. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  29. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  30. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  31. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  32. data/lib/childprocess/unix/process.rb +90 -89
  33. data/lib/childprocess/version.rb +3 -3
  34. data/lib/childprocess/windows.rb +38 -33
  35. data/lib/childprocess/windows/handle.rb +91 -91
  36. data/lib/childprocess/windows/io.rb +25 -25
  37. data/lib/childprocess/windows/lib.rb +416 -416
  38. data/lib/childprocess/windows/process.rb +131 -130
  39. data/lib/childprocess/windows/process_builder.rb +178 -178
  40. data/lib/childprocess/windows/structs.rb +148 -148
  41. data/spec/abstract_io_spec.rb +12 -12
  42. data/spec/childprocess_spec.rb +447 -447
  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 -270
  48. data/spec/unix_spec.rb +57 -57
  49. data/spec/windows_spec.rb +23 -23
  50. metadata +12 -26
  51. data/ext/mkrf_conf.rb +0 -24
@@ -1,37 +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 MissingFFIError < Error
18
- def initialize
19
- message = "FFI is a required pre-requisite for posix_spawn, falling back to default implementation. " +
20
- "Please add it to your deployment to unlock this functionality. " +
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
+ 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,184 +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
- # 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
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