Class: Buildbox::Command
- Inherits:
-
Object
- Object
- Buildbox::Command
- Defined in:
- lib/buildbox/command.rb
Defined Under Namespace
Classes: TimeoutExceeded
Constant Summary collapse
- READ_CHUNK_SIZE =
The chunk size for reading from subprocess IO.
4096
Instance Attribute Summary collapse
-
#exit_status ⇒ Object
readonly
Returns the value of attribute exit_status.
-
#output ⇒ Object
readonly
Returns the value of attribute output.
Class Method Summary collapse
Instance Method Summary collapse
- #arguments ⇒ Object
-
#initialize(*args) ⇒ Command
constructor
A new instance of Command.
- #process ⇒ Object
- #start(&block) ⇒ Object
Constructor Details
Instance Attribute Details
#exit_status ⇒ Object (readonly)
Returns the value of attribute exit_status.
20 21 22 |
# File 'lib/buildbox/command.rb', line 20 def exit_status @exit_status end |
#output ⇒ Object (readonly)
Returns the value of attribute output.
20 21 22 |
# File 'lib/buildbox/command.rb', line 20 def output @output end |
Class Method Details
.run(*args, &block) ⇒ Object
22 23 24 25 26 |
# File 'lib/buildbox/command.rb', line 22 def self.run(*args, &block) command = new(*args, &block) command.start(&block) command end |
Instance Method Details
#arguments ⇒ Object
34 35 36 |
# File 'lib/buildbox/command.rb', line 34 def arguments [ *@arguments ].compact.map(&:to_s) # all arguments must be a string end |
#process ⇒ Object
38 39 40 |
# File 'lib/buildbox/command.rb', line 38 def process @process ||= ChildProcess.build(*arguments) end |
#start(&block) ⇒ Object
42 43 44 45 46 47 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 107 108 109 110 111 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 |
# File 'lib/buildbox/command.rb', line 42 def start(&block) # Get the timeout, if we have one timeout = @options[:timeout] # Set the directory for the process process.cwd = File.(@options[:directory] || Dir.pwd) # Create the pipes so we can read the output in real time. PTY # isn't avaible on all platforms (heroku) so we just fallback to IO.pipe # if it's not presetnt. read_pipe, write_pipe = begin PTY.open rescue IO.pipe end process.io.stdout = write_pipe process.io.stderr = write_pipe process.duplex = true # Set the environment on the process if @options[:environment] @options[:environment].each_pair do |key, value| process.environment[key] = value end end # Start the process process.start # Make sure the stdin does not buffer process.io.stdin.sync = true @logger.debug("Process #{arguments} started with PID: #{process.pid}") if RUBY_PLATFORM != "java" # On Java, we have to close after. See down the method... # Otherwise, we close the writer right here, since we're # not on the writing side. write_pipe.close end # Record the start time for timeout purposes start_time = Time.now.to_i # Track the output as it goes output = "" @logger.debug("Selecting on IO") while true results = IO.select([read_pipe], nil, nil, timeout || 0.1) || [] readers = results[0] # Check if we have exceeded our timeout raise TimeoutExceeded if timeout && (Time.now.to_i - start_time) > timeout # Kill the process and wait a bit for it to disappear # Process.kill('KILL', process.pid) # Process.waitpid2(process.pid) # Check the readers to see if they're ready if readers && !readers.empty? readers.each do |r| # Read from the IO object data = read_io(r) # We don't need to do anything if the data is empty next if data.empty? output << cleaned_data = UTF8.clean(data) yield cleaned_data if block_given? end end # Break out if the process exited. We have to do this before # attempting to write to stdin otherwise we'll get a broken pipe # error. break if process.exited? end # Wait for the process to end. begin remaining = (timeout || 32000) - (Time.now.to_i - start_time) remaining = 0 if remaining < 0 @logger.debug("Waiting for process to exit. Remaining to timeout: #{remaining}") process.poll_for_exit(remaining) rescue ChildProcess::TimeoutError raise TimeoutExceeded end @logger.debug("Exit status: #{process.exit_code}") # Read the final output data, since it is possible we missed a small # amount of text between the time we last read data and when the # process exited. # Read the extra data extra_data = read_io(read_pipe) # If there's some that we missed if extra_data != "" output << cleaned_data = UTF8.clean(extra_data) yield cleaned_data if block_given? end if RUBY_PLATFORM == "java" # On JRuby, we need to close the writers after the process, # for some reason. See https://github.com/mitchellh/vagrant/pull/711 write_pipe.close end @output = output.chomp @exit_status = process.exit_code end |