buildbox 0.2.3 → 0.3
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 +4 -4
- data/Gemfile.lock +1 -1
- data/bin/buildbox-pty +3 -0
- data/lib/buildbox.rb +5 -0
- data/lib/buildbox/command.rb +175 -30
- data/lib/buildbox/platform.rb +23 -0
- data/lib/buildbox/runner.rb +2 -2
- data/lib/buildbox/version.rb +1 -1
- data/spec/buildbox/buildbox/command_spec.rb +22 -12
- data/spec/fixtures/sleep_script +6 -0
- data/spec/support/silence_logger.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 943e5240e1dc1d41d6086cdf88dadc990477f165
|
4
|
+
data.tar.gz: 7f03003bb7c71db8971a5c313325fc5461cca2dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 581a02441d7887327dd69584eb0c476b90dfcd01899e81f3da7bafe03b7d505ffbf4e3af385154d17394fe0db255eed477d098f41fe7ff3ae18ce502252fafce
|
7
|
+
data.tar.gz: c9fec973a2d1814c2342a719d14861cb0478ecdcabf7723c5e3085144430e1fea8fc35e42082b40071a723beadaaedc3413e28b297902920cc30bb4977621b1d
|
data/Gemfile.lock
CHANGED
data/bin/buildbox-pty
CHANGED
data/lib/buildbox.rb
CHANGED
@@ -8,6 +8,7 @@ module Buildbox
|
|
8
8
|
autoload :CLI, "buildbox/cli"
|
9
9
|
autoload :Configuration, "buildbox/configuration"
|
10
10
|
autoload :Monitor, "buildbox/monitor"
|
11
|
+
autoload :Platform, "buildbox/platform"
|
11
12
|
autoload :Runner, "buildbox/runner"
|
12
13
|
autoload :Script, "buildbox/script"
|
13
14
|
autoload :Server, "buildbox/server"
|
@@ -23,6 +24,10 @@ module Buildbox
|
|
23
24
|
@logger ||= Logger.new(STDOUT).tap { |logger| logger.level = Logger::INFO }
|
24
25
|
end
|
25
26
|
|
27
|
+
def self.logger=(logger)
|
28
|
+
@logger = logger
|
29
|
+
end
|
30
|
+
|
26
31
|
def self.gem_root
|
27
32
|
path = File.expand_path(File.join(__FILE__, "..", ".."))
|
28
33
|
end
|
data/lib/buildbox/command.rb
CHANGED
@@ -1,56 +1,146 @@
|
|
1
1
|
require 'childprocess'
|
2
2
|
require 'pty'
|
3
3
|
|
4
|
+
# Inspiration from:
|
5
|
+
# https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/subprocess.rb
|
6
|
+
|
4
7
|
module Buildbox
|
5
8
|
class Command
|
6
|
-
|
7
|
-
|
9
|
+
# The chunk size for reading from subprocess IO.
|
10
|
+
READ_CHUNK_SIZE = 4096
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
+
# An error which occurs when the process doesn't end within
|
13
|
+
# the given timeout.
|
14
|
+
class TimeoutExceeded < StandardError; end
|
15
|
+
|
16
|
+
attr_reader :output, :exit_status
|
12
17
|
|
13
|
-
def self.
|
14
|
-
|
18
|
+
def self.run(*args, &block)
|
19
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
20
|
+
arguments = args.dup
|
21
|
+
|
22
|
+
# Run the command
|
23
|
+
command = new(arguments, options, &block)
|
24
|
+
command.start(&block)
|
25
|
+
command
|
15
26
|
end
|
16
27
|
|
17
28
|
def initialize(arguments, options = {})
|
18
|
-
@arguments
|
19
|
-
@
|
20
|
-
@
|
29
|
+
@arguments = arguments
|
30
|
+
@options = options
|
31
|
+
@logger = Buildbox.logger
|
21
32
|
end
|
22
33
|
|
23
34
|
def start(&block)
|
24
|
-
|
35
|
+
# Get the timeout, if we have one
|
36
|
+
timeout = @options[:timeout]
|
37
|
+
|
38
|
+
# Build the command we're going to run
|
39
|
+
arguments = [ *runner, *@arguments ].compact.map(&:to_s) # all arguments must be a string
|
40
|
+
|
41
|
+
# Build the ChildProcess
|
42
|
+
@logger.info("Starting process: #{arguments}")
|
25
43
|
|
26
|
-
|
27
|
-
process
|
28
|
-
process.cwd = expanded_directory
|
29
|
-
process.io.stdout = process.io.stderr = write_io
|
44
|
+
process = ChildProcess.build(*arguments)
|
45
|
+
process.cwd = File.expand_path(@options[:directory] || Dir.pwd)
|
30
46
|
|
31
|
-
|
32
|
-
|
47
|
+
# Create the pipes so we can read the output in real tim
|
48
|
+
read_pipe, write_pipe = IO.pipe
|
49
|
+
process.io.stdout = write_pipe
|
50
|
+
process.io.stderr = write_pipe
|
51
|
+
process.duplex = true
|
52
|
+
|
53
|
+
# Set the environment on the process
|
54
|
+
if @options[:environment]
|
55
|
+
@options[:environment].each_pair do |key, value|
|
56
|
+
process.environment[key] = value
|
57
|
+
end
|
33
58
|
end
|
34
59
|
|
60
|
+
# Start the process
|
35
61
|
process.start
|
36
|
-
write_io.close
|
37
62
|
|
63
|
+
# Make sure the stdin does not buffer
|
64
|
+
process.io.stdin.sync = true
|
65
|
+
|
66
|
+
if RUBY_PLATFORM != "java"
|
67
|
+
# On Java, we have to close after. See down the method...
|
68
|
+
# Otherwise, we close the writer right here, since we're
|
69
|
+
# not on the writing side.
|
70
|
+
write_pipe.close
|
71
|
+
end
|
72
|
+
|
73
|
+
# Record the start time for timeout purposes
|
74
|
+
start_time = Time.now.to_i
|
75
|
+
|
76
|
+
# Track the output as it goes
|
38
77
|
output = ""
|
39
|
-
begin
|
40
|
-
loop do
|
41
|
-
chunk = read_io.readpartial(10240)
|
42
|
-
cleaned_chunk = UTF8.clean(chunk)
|
43
78
|
|
44
|
-
|
45
|
-
|
79
|
+
@logger.debug("Selecting on IO")
|
80
|
+
while true
|
81
|
+
results = IO.select([read_pipe], nil, nil, timeout || 0.1) || []
|
82
|
+
readers = results[0]
|
83
|
+
|
84
|
+
# Check if we have exceeded our timeout
|
85
|
+
raise TimeoutExceeded if timeout && (Time.now.to_i - start_time) > timeout
|
86
|
+
# Kill the process and wait a bit for it to disappear
|
87
|
+
# Process.kill('KILL', process.pid)
|
88
|
+
# Process.waitpid2(process.pid)
|
89
|
+
|
90
|
+
# Check the readers to see if they're ready
|
91
|
+
if readers && !readers.empty?
|
92
|
+
readers.each do |r|
|
93
|
+
# Read from the IO object
|
94
|
+
data = read_io(r)
|
95
|
+
|
96
|
+
# We don't need to do anything if the data is empty
|
97
|
+
next if data.empty?
|
98
|
+
|
99
|
+
output << cleaned_data = UTF8.clean(data)
|
100
|
+
yield cleaned_data if block_given?
|
101
|
+
end
|
46
102
|
end
|
47
|
-
|
103
|
+
|
104
|
+
# Break out if the process exited. We have to do this before
|
105
|
+
# attempting to write to stdin otherwise we'll get a broken pipe
|
106
|
+
# error.
|
107
|
+
break if process.exited?
|
108
|
+
end
|
109
|
+
|
110
|
+
# Wait for the process to end.
|
111
|
+
begin
|
112
|
+
remaining = (timeout || 32000) - (Time.now.to_i - start_time)
|
113
|
+
remaining = 0 if remaining < 0
|
114
|
+
@logger.debug("Waiting for process to exit. Remaining to timeout: #{remaining}")
|
115
|
+
|
116
|
+
process.poll_for_exit(remaining)
|
117
|
+
rescue ChildProcess::TimeoutError
|
118
|
+
raise TimeoutExceeded
|
119
|
+
end
|
120
|
+
|
121
|
+
@logger.debug("Exit status: #{process.exit_code}")
|
122
|
+
|
123
|
+
# Read the final output data, since it is possible we missed a small
|
124
|
+
# amount of text between the time we last read data and when the
|
125
|
+
# process exited.
|
126
|
+
|
127
|
+
# Read the extra data
|
128
|
+
extra_data = read_io(read_pipe)
|
129
|
+
|
130
|
+
# If there's some that we missed
|
131
|
+
if extra_data != ""
|
132
|
+
output << cleaned_data = UTF8.clean(extra_data)
|
133
|
+
yield cleaned_data if block_given?
|
48
134
|
end
|
49
135
|
|
50
|
-
|
136
|
+
if RUBY_PLATFORM == "java"
|
137
|
+
# On JRuby, we need to close the writers after the process,
|
138
|
+
# for some reason. See https://github.com/mitchellh/vagrant/pull/711
|
139
|
+
write_pipe.close
|
140
|
+
end
|
51
141
|
|
52
|
-
|
53
|
-
|
142
|
+
@output = output.chomp
|
143
|
+
@exit_status = process.exit_code
|
54
144
|
end
|
55
145
|
|
56
146
|
private
|
@@ -67,8 +157,63 @@ module Buildbox
|
|
67
157
|
[ "bash", "-c" ]
|
68
158
|
end
|
69
159
|
|
70
|
-
|
71
|
-
|
160
|
+
# Reads data from an IO object while it can, returning the data it reads.
|
161
|
+
# When it encounters a case when it can't read anymore, it returns the
|
162
|
+
# data.
|
163
|
+
#
|
164
|
+
# @return [String]
|
165
|
+
def read_io(io)
|
166
|
+
data = ""
|
167
|
+
|
168
|
+
while true
|
169
|
+
begin
|
170
|
+
if Platform.windows?
|
171
|
+
# Windows doesn't support non-blocking reads on
|
172
|
+
# file descriptors or pipes so we have to get
|
173
|
+
# a bit more creative.
|
174
|
+
|
175
|
+
# Check if data is actually ready on this IO device.
|
176
|
+
# We have to do this since `readpartial` will actually block
|
177
|
+
# until data is available, which can cause blocking forever
|
178
|
+
# in some cases.
|
179
|
+
results = IO.select([io], nil, nil, 0.1)
|
180
|
+
break if !results || results[0].empty?
|
181
|
+
|
182
|
+
# Read!
|
183
|
+
data << io.readpartial(READ_CHUNK_SIZE)
|
184
|
+
else
|
185
|
+
# Do a simple non-blocking read on the IO object
|
186
|
+
data << io.read_nonblock(READ_CHUNK_SIZE)
|
187
|
+
end
|
188
|
+
rescue Exception => e
|
189
|
+
# The catch-all rescue here is to support multiple Ruby versions,
|
190
|
+
# since we use some Ruby 1.9 specific exceptions.
|
191
|
+
|
192
|
+
breakable = false
|
193
|
+
if e.is_a?(EOFError)
|
194
|
+
# An `EOFError` means this IO object is done!
|
195
|
+
breakable = true
|
196
|
+
elsif defined?(IO::WaitReadable) && e.is_a?(IO::WaitReadable)
|
197
|
+
# IO::WaitReadable is only available on Ruby 1.9+
|
198
|
+
|
199
|
+
# An IO::WaitReadable means there may be more IO but this
|
200
|
+
# IO object is not ready to be read from yet. No problem,
|
201
|
+
# we read as much as we can, so we break.
|
202
|
+
breakable = true
|
203
|
+
elsif e.is_a?(Errno::EAGAIN)
|
204
|
+
# Otherwise, we just look for the EAGAIN error which should be
|
205
|
+
# all that IO::WaitReadable does in Ruby 1.9.
|
206
|
+
breakable = true
|
207
|
+
end
|
208
|
+
|
209
|
+
# Break out if we're supposed to. Otherwise re-raise the error
|
210
|
+
# because it is a real problem.
|
211
|
+
break if breakable
|
212
|
+
raise
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
data
|
72
217
|
end
|
73
218
|
end
|
74
219
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Buildbox
|
2
|
+
class Platform
|
3
|
+
class << self
|
4
|
+
[:cygwin, :darwin, :bsd, :freebsd, :linux, :solaris].each do |type|
|
5
|
+
define_method("#{type}?") do
|
6
|
+
platform.include?(type.to_s)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def windows?
|
11
|
+
%W[mingw mswin].each do |text|
|
12
|
+
return true if platform.include?(text)
|
13
|
+
end
|
14
|
+
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def platform
|
19
|
+
RbConfig::CONFIG["host_os"].downcase
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/buildbox/runner.rb
CHANGED
@@ -23,8 +23,8 @@ module Buildbox
|
|
23
23
|
info "Running script: #{script_path}"
|
24
24
|
|
25
25
|
build.output = ""
|
26
|
-
result = Command.
|
27
|
-
|
26
|
+
result = Command.run(script_path, :environment => @build.env,
|
27
|
+
:directory => directory_path) do |chunk|
|
28
28
|
build.output << chunk
|
29
29
|
end
|
30
30
|
|
data/lib/buildbox/version.rb
CHANGED
@@ -5,34 +5,34 @@ require "spec_helper"
|
|
5
5
|
describe Buildbox::Command do
|
6
6
|
describe "#run" do
|
7
7
|
it "is run within a tty" do
|
8
|
-
result = Buildbox::Command.
|
8
|
+
result = Buildbox::Command.run(%{ruby -e "puts STDOUT.tty?"})
|
9
9
|
|
10
10
|
result.output.should == "true"
|
11
11
|
end
|
12
12
|
|
13
13
|
it "successfully runs and returns the output from a simple comment" do
|
14
|
-
result = Buildbox::Command.
|
14
|
+
result = Buildbox::Command.run('echo hello world')
|
15
15
|
|
16
16
|
result.exit_status.should == 0
|
17
17
|
result.output.should == "hello world"
|
18
18
|
end
|
19
19
|
|
20
20
|
it "redirects stdout to stderr" do
|
21
|
-
result = Buildbox::Command.
|
21
|
+
result = Buildbox::Command.run('echo hello world 1>&2')
|
22
22
|
|
23
23
|
result.exit_status.should == 0
|
24
24
|
result.output.should == "hello world"
|
25
25
|
end
|
26
26
|
|
27
27
|
it "handles commands that fail and returns the correct status" do
|
28
|
-
result = Buildbox::Command.
|
28
|
+
result = Buildbox::Command.run('(exit 1)')
|
29
29
|
|
30
30
|
result.exit_status.should_not == 0
|
31
31
|
result.output.should == ''
|
32
32
|
end
|
33
33
|
|
34
34
|
it "handles running malformed commands" do
|
35
|
-
result = Buildbox::Command.
|
35
|
+
result = Buildbox::Command.run('if (')
|
36
36
|
|
37
37
|
result.exit_status.should_not == 0
|
38
38
|
# bash 3.2.48 prints "syntax error" in lowercase.
|
@@ -45,7 +45,7 @@ describe Buildbox::Command do
|
|
45
45
|
|
46
46
|
it "can collect output in chunks" do
|
47
47
|
chunked_output = ''
|
48
|
-
result = Buildbox::Command.
|
48
|
+
result = Buildbox::Command.run('echo hello world') do |chunk|
|
49
49
|
unless chunk.nil?
|
50
50
|
chunked_output += chunk
|
51
51
|
end
|
@@ -60,8 +60,8 @@ describe Buildbox::Command do
|
|
60
60
|
result = nil
|
61
61
|
second_result = nil
|
62
62
|
thread = Thread.new do
|
63
|
-
result = Buildbox::Command.
|
64
|
-
second_result = Buildbox::Command.
|
63
|
+
result = Buildbox::Command.run('sillycommandlololol')
|
64
|
+
second_result = Buildbox::Command.run('export FOO=bar; doesntexist.rb')
|
65
65
|
end
|
66
66
|
thread.join
|
67
67
|
|
@@ -76,7 +76,7 @@ describe Buildbox::Command do
|
|
76
76
|
|
77
77
|
it "captures color'd output from a command" do
|
78
78
|
chunked_output = ''
|
79
|
-
result = Buildbox::Command.
|
79
|
+
result = Buildbox::Command.run("rspec #{FIXTURES_PATH.join('rspec', 'test_spec.rb')}") do |chunk|
|
80
80
|
chunked_output += chunk unless chunk.nil?
|
81
81
|
end
|
82
82
|
|
@@ -87,7 +87,7 @@ describe Buildbox::Command do
|
|
87
87
|
|
88
88
|
it "runs scripts in a tty" do
|
89
89
|
chunked_output = ''
|
90
|
-
result = Buildbox::Command.
|
90
|
+
result = Buildbox::Command.run(FIXTURES_PATH.join('tty_script')) do |chunk|
|
91
91
|
chunked_output += chunk unless chunk.nil?
|
92
92
|
end
|
93
93
|
|
@@ -97,19 +97,29 @@ describe Buildbox::Command do
|
|
97
97
|
|
98
98
|
it "still runs even if pty isn't available" do
|
99
99
|
PTY.should_receive(:spawn).and_raise(RuntimeError.new)
|
100
|
-
result = Buildbox::Command.
|
100
|
+
result = Buildbox::Command.run('echo hello world')
|
101
101
|
|
102
102
|
result.exit_status.should == 0
|
103
103
|
result.output.should == "hello world"
|
104
104
|
end
|
105
105
|
|
106
106
|
it "supports utf8 characters" do
|
107
|
-
result = Buildbox::Command.
|
107
|
+
result = Buildbox::Command.run('bash', '-c', 'echo "hello"; echo "\xE2\x98\xA0"')
|
108
108
|
|
109
109
|
result.exit_status.should == 0
|
110
110
|
# just trying to interact with the string that has utf8 in it to make sure that it
|
111
111
|
# doesn't blow up like it doesn on osx. this is hacky - need a better test.
|
112
112
|
added = result.output + "hello"
|
113
113
|
end
|
114
|
+
|
115
|
+
it "can collect chunks from within a thread" do
|
116
|
+
chunks = []
|
117
|
+
|
118
|
+
result = Buildbox::Command.run(FIXTURES_PATH.join('sleep_script')) do |chunk|
|
119
|
+
chunks << chunk
|
120
|
+
end
|
121
|
+
|
122
|
+
chunks.should == ["0\r\n", "1\r\n", "2\r\n"]
|
123
|
+
end
|
114
124
|
end
|
115
125
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: buildbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keith Pitt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- lib/buildbox/command.rb
|
122
122
|
- lib/buildbox/configuration.rb
|
123
123
|
- lib/buildbox/monitor.rb
|
124
|
+
- lib/buildbox/platform.rb
|
124
125
|
- lib/buildbox/runner.rb
|
125
126
|
- lib/buildbox/server.rb
|
126
127
|
- lib/buildbox/utf8.rb
|
@@ -157,6 +158,7 @@ files:
|
|
157
158
|
- spec/fixtures/repo.git/objects/e9/8d8a9be514ef53609a52c9e1b820dbcc8e6603
|
158
159
|
- spec/fixtures/repo.git/refs/heads/master
|
159
160
|
- spec/fixtures/rspec/test_spec.rb
|
161
|
+
- spec/fixtures/sleep_script
|
160
162
|
- spec/fixtures/tty_script
|
161
163
|
- spec/integration/running_a_build_spec.rb
|
162
164
|
- spec/spec_helper.rb
|
@@ -219,6 +221,7 @@ test_files:
|
|
219
221
|
- spec/fixtures/repo.git/objects/e9/8d8a9be514ef53609a52c9e1b820dbcc8e6603
|
220
222
|
- spec/fixtures/repo.git/refs/heads/master
|
221
223
|
- spec/fixtures/rspec/test_spec.rb
|
224
|
+
- spec/fixtures/sleep_script
|
222
225
|
- spec/fixtures/tty_script
|
223
226
|
- spec/integration/running_a_build_spec.rb
|
224
227
|
- spec/spec_helper.rb
|