childprocess 0.8.0 → 3.0.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.
- checksums.yaml +5 -5
- data/.document +6 -6
- data/.gitignore +28 -28
- data/.rspec +1 -1
- data/.travis.yml +40 -36
- data/CHANGELOG.md +73 -44
- data/Gemfile +21 -15
- data/LICENSE +20 -20
- data/README.md +218 -192
- data/Rakefile +61 -61
- data/appveyor.yml +42 -43
- data/childprocess.gemspec +26 -30
- data/lib/childprocess.rb +210 -205
- data/lib/childprocess/abstract_io.rb +36 -36
- data/lib/childprocess/abstract_process.rb +192 -192
- data/lib/childprocess/errors.rb +37 -26
- data/lib/childprocess/jruby.rb +56 -56
- data/lib/childprocess/jruby/io.rb +16 -16
- data/lib/childprocess/jruby/process.rb +184 -159
- data/lib/childprocess/jruby/pump.rb +53 -53
- data/lib/childprocess/tools/generator.rb +145 -145
- data/lib/childprocess/unix.rb +9 -9
- data/lib/childprocess/unix/fork_exec_process.rb +78 -70
- data/lib/childprocess/unix/io.rb +21 -21
- data/lib/childprocess/unix/lib.rb +186 -186
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
- data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
- data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
- data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
- data/lib/childprocess/unix/process.rb +90 -89
- data/lib/childprocess/version.rb +3 -3
- data/lib/childprocess/windows.rb +38 -33
- data/lib/childprocess/windows/handle.rb +91 -91
- data/lib/childprocess/windows/io.rb +25 -25
- data/lib/childprocess/windows/lib.rb +416 -416
- data/lib/childprocess/windows/process.rb +130 -130
- data/lib/childprocess/windows/process_builder.rb +178 -175
- data/lib/childprocess/windows/structs.rb +148 -148
- data/spec/abstract_io_spec.rb +12 -12
- data/spec/childprocess_spec.rb +447 -391
- data/spec/get_env.ps1 +13 -0
- data/spec/io_spec.rb +228 -228
- data/spec/jruby_spec.rb +24 -24
- data/spec/pid_behavior.rb +12 -12
- data/spec/platform_detection_spec.rb +86 -86
- data/spec/spec_helper.rb +270 -261
- data/spec/unix_spec.rb +57 -57
- data/spec/windows_spec.rb +23 -23
- metadata +8 -39
data/lib/childprocess/errors.rb
CHANGED
@@ -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
|
18
|
-
def initialize
|
19
|
-
message = "
|
20
|
-
"
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
data/lib/childprocess/jruby.rb
CHANGED
@@ -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
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
def
|
112
|
-
@
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
+
# 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
|