childprocess 0.8.0 → 2.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.
Files changed (51) 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 +42 -36
  6. data/CHANGELOG.md +67 -44
  7. data/Gemfile +18 -15
  8. data/LICENSE +20 -20
  9. data/README.md +216 -192
  10. data/Rakefile +61 -61
  11. data/appveyor.yml +42 -43
  12. data/childprocess.gemspec +32 -30
  13. data/ext/mkrf_conf.rb +24 -0
  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/io.rb +16 -16
  18. data/lib/childprocess/jruby/process.rb +184 -159
  19. data/lib/childprocess/jruby/pump.rb +53 -53
  20. data/lib/childprocess/jruby.rb +56 -56
  21. data/lib/childprocess/tools/generator.rb +145 -145
  22. data/lib/childprocess/unix/fork_exec_process.rb +78 -70
  23. data/lib/childprocess/unix/io.rb +21 -21
  24. data/lib/childprocess/unix/lib.rb +186 -186
  25. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  26. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  27. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  28. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  29. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  30. data/lib/childprocess/unix/process.rb +90 -89
  31. data/lib/childprocess/unix.rb +9 -9
  32. data/lib/childprocess/version.rb +3 -3
  33. data/lib/childprocess/windows/handle.rb +91 -91
  34. data/lib/childprocess/windows/io.rb +25 -25
  35. data/lib/childprocess/windows/lib.rb +416 -416
  36. data/lib/childprocess/windows/process.rb +130 -130
  37. data/lib/childprocess/windows/process_builder.rb +178 -175
  38. data/lib/childprocess/windows/structs.rb +148 -148
  39. data/lib/childprocess/windows.rb +33 -33
  40. data/lib/childprocess.rb +210 -205
  41. data/spec/abstract_io_spec.rb +12 -12
  42. data/spec/childprocess_spec.rb +447 -391
  43. data/spec/get_env.ps1 +13 -0
  44. data/spec/io_spec.rb +228 -228
  45. data/spec/jruby_spec.rb +24 -24
  46. data/spec/pid_behavior.rb +12 -12
  47. data/spec/platform_detection_spec.rb +86 -86
  48. data/spec/spec_helper.rb +270 -261
  49. data/spec/unix_spec.rb +57 -57
  50. data/spec/windows_spec.rb +23 -23
  51. metadata +18 -33
@@ -1,130 +1,130 @@
1
- module ChildProcess
2
- module Windows
3
- class Process < AbstractProcess
4
-
5
- attr_reader :pid
6
-
7
- def io
8
- @io ||= Windows::IO.new
9
- end
10
-
11
- def stop(timeout = 3)
12
- assert_started
13
-
14
- log "sending KILL"
15
- @handle.send(WIN_SIGKILL)
16
-
17
- poll_for_exit(timeout)
18
- ensure
19
- close_handle
20
- close_job_if_necessary
21
- end
22
-
23
- def wait
24
- if exited?
25
- exit_code
26
- else
27
- @handle.wait
28
- @exit_code = @handle.exit_code
29
-
30
- close_handle
31
- close_job_if_necessary
32
-
33
- @exit_code
34
- end
35
- end
36
-
37
- def exited?
38
- return true if @exit_code
39
- assert_started
40
-
41
- code = @handle.exit_code
42
- exited = code != PROCESS_STILL_ACTIVE
43
-
44
- log(:exited? => exited, :code => code)
45
-
46
- if exited
47
- @exit_code = code
48
- close_handle
49
- close_job_if_necessary
50
- end
51
-
52
- exited
53
- end
54
-
55
- private
56
-
57
- def launch_process
58
- builder = ProcessBuilder.new(@args)
59
- builder.leader = leader?
60
- builder.detach = detach?
61
- builder.duplex = duplex?
62
- builder.environment = @environment unless @environment.empty?
63
- builder.cwd = @cwd
64
-
65
- if @io
66
- builder.stdout = @io.stdout
67
- builder.stderr = @io.stderr
68
- end
69
-
70
- @pid = builder.start
71
- @handle = Handle.open @pid
72
-
73
- if leader?
74
- @job = Job.new
75
- @job << @handle
76
- end
77
-
78
- if duplex?
79
- raise Error, "no stdin stream" unless builder.stdin
80
- io._stdin = builder.stdin
81
- end
82
-
83
- self
84
- end
85
-
86
- def close_handle
87
- @handle.close
88
- end
89
-
90
- def close_job_if_necessary
91
- @job.close if leader?
92
- end
93
-
94
-
95
- class Job
96
- def initialize
97
- @pointer = Lib.create_job_object(nil, nil)
98
-
99
- if @pointer.nil? || @pointer.null?
100
- raise Error, "unable to create job object"
101
- end
102
-
103
- basic = JobObjectBasicLimitInformation.new
104
- basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK
105
-
106
- extended = JobObjectExtendedLimitInformation.new
107
- extended[:BasicLimitInformation] = basic
108
-
109
- ret = Lib.set_information_job_object(
110
- @pointer,
111
- JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
112
- extended,
113
- extended.size
114
- )
115
-
116
- Lib.check_error ret
117
- end
118
-
119
- def <<(handle)
120
- Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
121
- end
122
-
123
- def close
124
- Lib.close_handle @pointer
125
- end
126
- end
127
-
128
- end # Process
129
- end # Windows
130
- end # ChildProcess
1
+ module ChildProcess
2
+ module Windows
3
+ class Process < AbstractProcess
4
+
5
+ attr_reader :pid
6
+
7
+ def io
8
+ @io ||= Windows::IO.new
9
+ end
10
+
11
+ def stop(timeout = 3)
12
+ assert_started
13
+
14
+ log "sending KILL"
15
+ @handle.send(WIN_SIGKILL)
16
+
17
+ poll_for_exit(timeout)
18
+ ensure
19
+ close_handle
20
+ close_job_if_necessary
21
+ end
22
+
23
+ def wait
24
+ if exited?
25
+ exit_code
26
+ else
27
+ @handle.wait
28
+ @exit_code = @handle.exit_code
29
+
30
+ close_handle
31
+ close_job_if_necessary
32
+
33
+ @exit_code
34
+ end
35
+ end
36
+
37
+ def exited?
38
+ return true if @exit_code
39
+ assert_started
40
+
41
+ code = @handle.exit_code
42
+ exited = code != PROCESS_STILL_ACTIVE
43
+
44
+ log(:exited? => exited, :code => code)
45
+
46
+ if exited
47
+ @exit_code = code
48
+ close_handle
49
+ close_job_if_necessary
50
+ end
51
+
52
+ exited
53
+ end
54
+
55
+ private
56
+
57
+ def launch_process
58
+ builder = ProcessBuilder.new(@args)
59
+ builder.leader = leader?
60
+ builder.detach = detach?
61
+ builder.duplex = duplex?
62
+ builder.environment = @environment unless @environment.empty?
63
+ builder.cwd = @cwd
64
+
65
+ if @io
66
+ builder.stdout = @io.stdout
67
+ builder.stderr = @io.stderr
68
+ end
69
+
70
+ @pid = builder.start
71
+ @handle = Handle.open @pid
72
+
73
+ if leader?
74
+ @job = Job.new
75
+ @job << @handle
76
+ end
77
+
78
+ if duplex?
79
+ raise Error, "no stdin stream" unless builder.stdin
80
+ io._stdin = builder.stdin
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ def close_handle
87
+ @handle.close
88
+ end
89
+
90
+ def close_job_if_necessary
91
+ @job.close if leader?
92
+ end
93
+
94
+
95
+ class Job
96
+ def initialize
97
+ @pointer = Lib.create_job_object(nil, nil)
98
+
99
+ if @pointer.nil? || @pointer.null?
100
+ raise Error, "unable to create job object"
101
+ end
102
+
103
+ basic = JobObjectBasicLimitInformation.new
104
+ basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK
105
+
106
+ extended = JobObjectExtendedLimitInformation.new
107
+ extended[:BasicLimitInformation] = basic
108
+
109
+ ret = Lib.set_information_job_object(
110
+ @pointer,
111
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
112
+ extended,
113
+ extended.size
114
+ )
115
+
116
+ Lib.check_error ret
117
+ end
118
+
119
+ def <<(handle)
120
+ Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
121
+ end
122
+
123
+ def close
124
+ Lib.close_handle @pointer
125
+ end
126
+ end
127
+
128
+ end # Process
129
+ end # Windows
130
+ end # ChildProcess
@@ -1,175 +1,178 @@
1
- module ChildProcess
2
- module Windows
3
- class ProcessBuilder
4
- attr_accessor :leader, :detach, :duplex, :environment, :stdout, :stderr, :cwd
5
- attr_reader :stdin
6
-
7
- def initialize(args)
8
- @args = args
9
-
10
- @detach = false
11
- @duplex = false
12
- @environment = nil
13
- @cwd = nil
14
-
15
- @stdout = nil
16
- @stderr = nil
17
- @stdin = nil
18
-
19
- @flags = 0
20
- @job_ptr = nil
21
- @cmd_ptr = nil
22
- @env_ptr = nil
23
- @cwd_ptr = nil
24
- end
25
-
26
- def start
27
- create_command_pointer
28
- create_environment_pointer
29
- create_cwd_pointer
30
-
31
- setup_flags
32
- setup_io
33
-
34
- pid = create_process
35
- close_handles
36
-
37
- pid
38
- end
39
-
40
- private
41
-
42
- def create_command_pointer
43
- string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' '
44
- @cmd_ptr = FFI::MemoryPointer.from_string string
45
- end
46
-
47
- def create_environment_pointer
48
- return unless @environment.kind_of?(Hash) && @environment.any?
49
-
50
- strings = []
51
-
52
- ENV.to_hash.merge(@environment).each do |key, val|
53
- next if val.nil?
54
-
55
- if key.to_s =~ /=|\0/ || val.to_s.include?("\0")
56
- raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
57
- end
58
-
59
- strings << "#{key}=#{val}\0"
60
- end
61
-
62
- strings << "\0" # terminate the env block
63
- env_str = strings.join
64
-
65
- @env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
66
- @env_ptr.put_bytes 0, env_str, 0, env_str.bytesize
67
- end
68
-
69
- def create_cwd_pointer
70
- @cwd_ptr = FFI::MemoryPointer.from_string(@cwd || Dir.pwd)
71
- end
72
-
73
- def create_process
74
- ok = Lib.create_process(
75
- nil, # application name
76
- @cmd_ptr, # command line
77
- nil, # process attributes
78
- nil, # thread attributes
79
- true, # inherit handles
80
- @flags, # creation flags
81
- @env_ptr, # environment
82
- @cwd_ptr, # current directory
83
- startup_info, # startup info
84
- process_info # process info
85
- )
86
-
87
- ok or raise LaunchError, Lib.last_error_message
88
-
89
- process_info[:dwProcessId]
90
- end
91
-
92
- def startup_info
93
- @startup_info ||= StartupInfo.new
94
- end
95
-
96
- def process_info
97
- @process_info ||= ProcessInfo.new
98
- end
99
-
100
- def setup_flags
101
- @flags |= DETACHED_PROCESS if @detach
102
- @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader
103
- end
104
-
105
- def setup_io
106
- startup_info[:dwFlags] ||= 0
107
- startup_info[:dwFlags] |= STARTF_USESTDHANDLES
108
-
109
- if @stdout
110
- startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
111
- end
112
-
113
- if @stderr
114
- startup_info[:hStdError] = std_stream_handle_for(@stderr)
115
- end
116
-
117
- if @duplex
118
- read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
119
- write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
120
- sa = SecurityAttributes.new(:inherit => true)
121
-
122
- ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
123
- Lib.check_error ok
124
-
125
- @read_pipe = read_pipe_ptr.read_pointer
126
- @write_pipe = write_pipe_ptr.read_pointer
127
-
128
- Lib.set_handle_inheritance @read_pipe, true
129
- Lib.set_handle_inheritance @write_pipe, false
130
-
131
- startup_info[:hStdInput] = @read_pipe
132
- else
133
- startup_info[:hStdInput] = std_stream_handle_for(STDIN)
134
- end
135
- end
136
-
137
- def std_stream_handle_for(io)
138
- handle = Lib.handle_for(io)
139
-
140
- begin
141
- Lib.set_handle_inheritance handle, true
142
- rescue ChildProcess::Error
143
- # If the IO was set to close on exec previously, this call will fail.
144
- # That's probably OK, since the user explicitly asked for it to be
145
- # closed (at least I have yet to find other cases where this will
146
- # happen...)
147
- end
148
-
149
- handle
150
- end
151
-
152
- def close_handles
153
- Lib.close_handle process_info[:hProcess]
154
- Lib.close_handle process_info[:hThread]
155
-
156
- if @duplex
157
- @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
158
- Lib.close_handle @read_pipe
159
- Lib.close_handle @write_pipe
160
- end
161
- end
162
-
163
- def quote_if_necessary(str)
164
- quote = str.start_with?('"') ? "'" : '"'
165
-
166
- case str
167
- when /[\s\\'"]/
168
- [quote, str, quote].join
169
- else
170
- str
171
- end
172
- end
173
- end # ProcessBuilder
174
- end # Windows
175
- end # ChildProcess
1
+ module ChildProcess
2
+ module Windows
3
+ class ProcessBuilder
4
+ attr_accessor :leader, :detach, :duplex, :environment, :stdout, :stderr, :cwd
5
+ attr_reader :stdin
6
+
7
+ def initialize(args)
8
+ @args = args
9
+
10
+ @detach = false
11
+ @duplex = false
12
+ @environment = nil
13
+ @cwd = nil
14
+
15
+ @stdout = nil
16
+ @stderr = nil
17
+ @stdin = nil
18
+
19
+ @flags = 0
20
+ @job_ptr = nil
21
+ @cmd_ptr = nil
22
+ @env_ptr = nil
23
+ @cwd_ptr = nil
24
+ end
25
+
26
+ def start
27
+ create_command_pointer
28
+ create_environment_pointer
29
+ create_cwd_pointer
30
+
31
+ setup_flags
32
+ setup_io
33
+
34
+ pid = create_process
35
+ close_handles
36
+
37
+ pid
38
+ end
39
+
40
+ private
41
+
42
+ def to_wide_string(str)
43
+ newstr = str + "\0".encode(str.encoding)
44
+ newstr.encode!('UTF-16LE')
45
+ end
46
+
47
+ def create_command_pointer
48
+ string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join(' ')
49
+ @cmd_ptr = to_wide_string(string)
50
+ end
51
+
52
+ def create_environment_pointer
53
+ return unless @environment.kind_of?(Hash) && @environment.any?
54
+
55
+ strings = []
56
+
57
+ ENV.to_hash.merge(@environment).each do |key, val|
58
+ next if val.nil?
59
+
60
+ if key.to_s =~ /=|\0/ || val.to_s.include?("\0")
61
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
62
+ end
63
+
64
+ strings << "#{key}=#{val}\0"
65
+ end
66
+
67
+ env_str = to_wide_string(strings.join)
68
+ @env_ptr = FFI::MemoryPointer.from_string(env_str)
69
+ end
70
+
71
+ def create_cwd_pointer
72
+ @cwd_ptr = FFI::MemoryPointer.from_string(to_wide_string(@cwd || Dir.pwd))
73
+ end
74
+
75
+ def create_process
76
+ ok = Lib.create_process(
77
+ nil, # application name
78
+ @cmd_ptr, # command line
79
+ nil, # process attributes
80
+ nil, # thread attributes
81
+ true, # inherit handles
82
+ @flags, # creation flags
83
+ @env_ptr, # environment
84
+ @cwd_ptr, # current directory
85
+ startup_info, # startup info
86
+ process_info # process info
87
+ )
88
+
89
+ ok or raise LaunchError, Lib.last_error_message
90
+
91
+ process_info[:dwProcessId]
92
+ end
93
+
94
+ def startup_info
95
+ @startup_info ||= StartupInfo.new
96
+ end
97
+
98
+ def process_info
99
+ @process_info ||= ProcessInfo.new
100
+ end
101
+
102
+ def setup_flags
103
+ @flags |= CREATE_UNICODE_ENVIRONMENT
104
+ @flags |= DETACHED_PROCESS if @detach
105
+ @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader
106
+ end
107
+
108
+ def setup_io
109
+ startup_info[:dwFlags] ||= 0
110
+ startup_info[:dwFlags] |= STARTF_USESTDHANDLES
111
+
112
+ if @stdout
113
+ startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
114
+ end
115
+
116
+ if @stderr
117
+ startup_info[:hStdError] = std_stream_handle_for(@stderr)
118
+ end
119
+
120
+ if @duplex
121
+ read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
122
+ write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
123
+ sa = SecurityAttributes.new(:inherit => true)
124
+
125
+ ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
126
+ Lib.check_error ok
127
+
128
+ @read_pipe = read_pipe_ptr.read_pointer
129
+ @write_pipe = write_pipe_ptr.read_pointer
130
+
131
+ Lib.set_handle_inheritance @read_pipe, true
132
+ Lib.set_handle_inheritance @write_pipe, false
133
+
134
+ startup_info[:hStdInput] = @read_pipe
135
+ else
136
+ startup_info[:hStdInput] = std_stream_handle_for(STDIN)
137
+ end
138
+ end
139
+
140
+ def std_stream_handle_for(io)
141
+ handle = Lib.handle_for(io)
142
+
143
+ begin
144
+ Lib.set_handle_inheritance handle, true
145
+ rescue ChildProcess::Error
146
+ # If the IO was set to close on exec previously, this call will fail.
147
+ # That's probably OK, since the user explicitly asked for it to be
148
+ # closed (at least I have yet to find other cases where this will
149
+ # happen...)
150
+ end
151
+
152
+ handle
153
+ end
154
+
155
+ def close_handles
156
+ Lib.close_handle process_info[:hProcess]
157
+ Lib.close_handle process_info[:hThread]
158
+
159
+ if @duplex
160
+ @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
161
+ Lib.close_handle @read_pipe
162
+ Lib.close_handle @write_pipe
163
+ end
164
+ end
165
+
166
+ def quote_if_necessary(str)
167
+ quote = str.start_with?('"') ? "'" : '"'
168
+
169
+ case str
170
+ when /[\s\\'"]/
171
+ [quote, str, quote].join
172
+ else
173
+ str
174
+ end
175
+ end
176
+ end # ProcessBuilder
177
+ end # Windows
178
+ end # ChildProcess