bob 0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -46
- data/Rakefile +10 -18
- data/bob.gemspec +15 -14
- data/deps.rip +1 -0
- data/lib/bob.rb +13 -15
- data/lib/bob/builder.rb +25 -36
- data/lib/bob/{background_engines.rb → engine.rb} +3 -3
- data/lib/bob/engine/threaded.rb +145 -0
- data/lib/bob/scm.rb +4 -12
- data/lib/bob/scm/abstract.rb +41 -27
- data/lib/bob/scm/git.rb +18 -44
- data/lib/bob/scm/svn.rb +30 -0
- data/lib/bob/test.rb +3 -0
- data/lib/bob/test/builder_stub.rb +34 -0
- data/lib/bob/test/repo.rb +163 -0
- data/test/bob_test.rb +3 -2
- data/test/deps.rip +3 -0
- data/test/engine/threaded_test.rb +52 -0
- data/test/git_test.rb +26 -0
- data/test/helper.rb +10 -13
- data/test/mixin/scm.rb +46 -0
- data/test/svn_test.rb +31 -0
- data/test/test_test.rb +26 -0
- metadata +19 -49
- data/lib/bob/background_engines/foreground.rb +0 -6
- data/lib/core_ext/object.rb +0 -7
- data/test/helper/buildable_stub.rb +0 -55
- data/test/helper/git_helper.rb +0 -48
data/README.rdoc
CHANGED
@@ -1,51 +1,6 @@
|
|
1
1
|
= Bob the Builder
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
* <tt>buildable.kind</tt>
|
6
|
-
|
7
|
-
Should return a Symbol with whatever kind of repository the buildable's code is
|
8
|
-
in (:git, :svn, etc).
|
9
|
-
|
10
|
-
* <tt>buildable.uri</tt>
|
11
|
-
|
12
|
-
Returns a string like "git://github.com/integrity/bob.git", pointing to the code
|
13
|
-
repository.
|
14
|
-
|
15
|
-
* <tt>buildable.branch</tt>
|
16
|
-
|
17
|
-
What branch of the repository should we build?
|
18
|
-
|
19
|
-
* <tt>buildable.build_script</tt>
|
20
|
-
|
21
|
-
Returns a string containing the build script to be run when "building".
|
22
|
-
|
23
|
-
* <tt>buildable.start_building(commit_id)</tt>
|
24
|
-
|
25
|
-
`commit_id` is a String that contains whatever is appropriate for the repo type,
|
26
|
-
so it would be a SHA1 hash for git repos, or a numeric id for svn, etc. This is a
|
27
|
-
callback so the buildable can determine how long it takes to build. It doesn't
|
28
|
-
need to return anything.
|
29
|
-
|
30
|
-
* <tt>buildable.finish_building(commit_id, build_status, build_output)</tt>
|
31
|
-
|
32
|
-
Callback for when the build finishes. It doesn't need to return anything. It will
|
33
|
-
receive a string with the commit identifier, a boolean for the build exit status
|
34
|
-
(true for successful builds, false fore failed ones) and a string with the build
|
35
|
-
output (both STDOUT and STDERR).
|
36
|
-
|
37
|
-
A successful build is one where the build script returns a zero status code.
|
38
|
-
|
39
|
-
Bob will, when called like:
|
40
|
-
|
41
|
-
Bob.build(buildable, commit_id)
|
42
|
-
|
43
|
-
1. Checkout the buildable on the specified commit
|
44
|
-
2. Call <tt>buildable.start_building</tt>
|
45
|
-
3. Run the script provided in <tt>build_script</tt> in the buildable.
|
46
|
-
4. When the build process finishes, it will call <tt>finish_building</tt> with
|
47
|
-
the commit_id, the build status (true if the script returns a status code
|
48
|
-
of 0, false otherwise), and a string with the build output (both STDOUT and STDERR).
|
3
|
+
Bob builds your code.
|
49
4
|
|
50
5
|
== Do I need this?
|
51
6
|
|
@@ -54,5 +9,6 @@ automated CI server, which is what most people need.
|
|
54
9
|
|
55
10
|
== Credits
|
56
11
|
|
12
|
+
Thanks a lot to raggi[http://github.com/raggi] for the threaded engine.
|
57
13
|
Authors:: Nicolas Sanguinetti (foca[http://github.com/foca]) and Simon Rozet (sr[http://github.com/sr])
|
58
14
|
License:: MIT (Check LICENSE for details)
|
data/Rakefile
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
require "rake/testtask"
|
2
2
|
|
3
|
+
rdoc_sources = %w(hanna/rdoctask rdoc/task rake/rdoctask)
|
3
4
|
begin
|
4
|
-
require
|
5
|
+
require rdoc_sources.shift
|
5
6
|
rescue LoadError
|
6
|
-
|
7
|
+
retry
|
7
8
|
end
|
8
9
|
|
9
10
|
begin
|
10
|
-
require "metric_fu"
|
11
|
+
require "metric_fu" if RUBY_VERSION < "1.9"
|
11
12
|
rescue LoadError
|
12
13
|
end
|
13
14
|
|
@@ -20,23 +21,14 @@ end
|
|
20
21
|
desc "Default: run all tests"
|
21
22
|
task :default => :test
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
ruby "test/bob_test.rb"
|
28
|
-
ruby "test/background_engine/threaded_test.rb"
|
24
|
+
desc "Run tests"
|
25
|
+
Rake::TestTask.new(:test) do |t|
|
26
|
+
t.test_files = FileList["test/*_test.rb"]
|
27
|
+
t.libs << "test"
|
29
28
|
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
task "test:#{scm}" do
|
34
|
-
ruby "test/scm/#{scm}_test.rb"
|
35
|
-
end
|
36
|
-
}
|
37
|
-
|
38
|
-
Rake::RDocTask.new do |rd|
|
39
|
-
rd.main = "README"
|
30
|
+
(defined?(RDoc::Task) ? RDoc::Task : Rake::RDocTask).new do |rd|
|
31
|
+
rd.main = "README.rdoc"
|
40
32
|
rd.title = "Documentation for Bob the Builder"
|
41
33
|
rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
|
42
34
|
rd.rdoc_dir = "doc"
|
data/bob.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "bob"
|
3
|
-
s.version = "0.
|
4
|
-
s.date = "2009-
|
3
|
+
s.version = "0.4.0"
|
4
|
+
s.date = "2009-10-10"
|
5
5
|
|
6
6
|
s.description = "Bob the Builder will build your code. Simple."
|
7
7
|
s.summary = "Bob builds!"
|
@@ -17,30 +17,31 @@ Gem::Specification.new do |s|
|
|
17
17
|
|
18
18
|
s.add_dependency "addressable"
|
19
19
|
|
20
|
-
if s.respond_to?(:add_development_dependency)
|
21
|
-
s.add_development_dependency "sr-mg"
|
22
|
-
s.add_development_dependency "contest"
|
23
|
-
s.add_development_dependency "redgreen"
|
24
|
-
s.add_development_dependency "ruby-debug"
|
25
|
-
end
|
26
|
-
|
27
20
|
s.files = %w[
|
28
21
|
.gitignore
|
29
22
|
LICENSE
|
30
23
|
README.rdoc
|
31
24
|
Rakefile
|
32
25
|
bob.gemspec
|
26
|
+
deps.rip
|
33
27
|
lib/bob.rb
|
34
|
-
lib/bob/background_engines.rb
|
35
|
-
lib/bob/background_engines/foreground.rb
|
36
28
|
lib/bob/builder.rb
|
29
|
+
lib/bob/engine.rb
|
30
|
+
lib/bob/engine/threaded.rb
|
37
31
|
lib/bob/scm.rb
|
38
32
|
lib/bob/scm/abstract.rb
|
39
33
|
lib/bob/scm/git.rb
|
40
|
-
lib/
|
34
|
+
lib/bob/scm/svn.rb
|
35
|
+
lib/bob/test.rb
|
36
|
+
lib/bob/test/builder_stub.rb
|
37
|
+
lib/bob/test/repo.rb
|
41
38
|
test/bob_test.rb
|
39
|
+
test/deps.rip
|
40
|
+
test/engine/threaded_test.rb
|
41
|
+
test/git_test.rb
|
42
42
|
test/helper.rb
|
43
|
-
test/
|
44
|
-
test/
|
43
|
+
test/mixin/scm.rb
|
44
|
+
test/svn_test.rb
|
45
|
+
test/test_test.rb
|
45
46
|
]
|
46
47
|
end
|
data/deps.rip
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
addressable 2.1.0
|
data/lib/bob.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "fileutils"
|
2
|
+
require "pathname"
|
2
3
|
require "yaml"
|
3
4
|
require "logger"
|
4
5
|
require "time"
|
@@ -6,31 +7,28 @@ require "addressable/uri"
|
|
6
7
|
|
7
8
|
require "bob/builder"
|
8
9
|
require "bob/scm"
|
9
|
-
require "bob/
|
10
|
+
require "bob/engine"
|
10
11
|
|
11
12
|
module Bob
|
12
|
-
|
13
|
-
|
14
|
-
def self.build(buildable, commit_ids)
|
15
|
-
Array(commit_ids).each do |commit_id|
|
16
|
-
Builder.new(buildable, commit_id).build
|
17
|
-
end
|
13
|
+
def self.build(buildable)
|
14
|
+
Builder.new(buildable).build
|
18
15
|
end
|
19
16
|
|
20
|
-
# Directory where the code for the different buildables will be checked out.
|
21
|
-
# the user running Bob is allowed to write to this directory.
|
17
|
+
# Directory where the code for the different buildables will be checked out.
|
18
|
+
# Make sure the user running Bob is allowed to write to this directory.
|
22
19
|
def self.directory
|
23
|
-
@directory || "/tmp"
|
20
|
+
Pathname(@directory || "/tmp")
|
24
21
|
end
|
25
22
|
|
26
|
-
# What will you use to build in background. Must respond to <tt>call</tt> and
|
27
|
-
# which will be run "in background". The default is to run in
|
23
|
+
# What will you use to build in background. Must respond to <tt>call</tt> and
|
24
|
+
# take a block which will be run "in background". The default is to run in
|
25
|
+
# foreground.
|
28
26
|
def self.engine
|
29
|
-
@engine ||
|
27
|
+
@engine || Engine::Foreground
|
30
28
|
end
|
31
29
|
|
32
|
-
# What to log with (must implement ruby's Logger interface). Logs to STDOUT
|
33
|
-
# default.
|
30
|
+
# What to log with (must implement ruby's Logger interface). Logs to STDOUT
|
31
|
+
# by default.
|
34
32
|
def self.logger
|
35
33
|
@logger || Logger.new(STDOUT)
|
36
34
|
end
|
data/lib/bob/builder.rb
CHANGED
@@ -1,53 +1,42 @@
|
|
1
1
|
module Bob
|
2
|
-
# A Builder will take care of building a buildable (wow, you didn't see that coming,
|
3
|
-
# right?).
|
4
2
|
class Builder
|
5
|
-
attr_reader :buildable
|
3
|
+
attr_reader :buildable
|
6
4
|
|
7
|
-
def initialize(buildable
|
5
|
+
def initialize(buildable)
|
8
6
|
@buildable = buildable
|
9
|
-
@commit_id = commit_id
|
10
7
|
end
|
11
8
|
|
12
|
-
# This is where the magic happens:
|
13
|
-
#
|
14
|
-
# 1. Check out the repo to the appropriate commit.
|
15
|
-
# 2. Notify the buildable that the build is starting.
|
16
|
-
# 3. Run the build script on it in the background.
|
17
|
-
# 4. Reports the build back to the buildable.
|
18
9
|
def build
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
build_status, build_output = run_build_script
|
24
|
-
buildable.finish_building(commit_id, build_status, build_output)
|
25
|
-
end
|
26
|
-
end
|
10
|
+
scm.with_commit(buildable["commit"]) { |commit|
|
11
|
+
started(scm.info(commit))
|
12
|
+
completed(*run)
|
13
|
+
}
|
27
14
|
end
|
28
15
|
|
29
|
-
|
16
|
+
protected
|
17
|
+
def started(commit_info)
|
18
|
+
end
|
30
19
|
|
31
|
-
|
32
|
-
|
20
|
+
def completed(status, output)
|
21
|
+
end
|
33
22
|
|
34
|
-
|
35
|
-
|
36
|
-
|
23
|
+
private
|
24
|
+
def run
|
25
|
+
output = ""
|
26
|
+
status = false
|
37
27
|
|
38
|
-
|
39
|
-
|
28
|
+
IO.popen(script, "r") { |io| output = io.read }
|
29
|
+
status = $?.success?
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
end
|
31
|
+
[status, output]
|
32
|
+
end
|
44
33
|
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
def script
|
35
|
+
"(cd #{scm.dir_for(buildable["commit"])} && #{buildable["command"]} 2>&1)"
|
36
|
+
end
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
|
38
|
+
def scm
|
39
|
+
@scm ||= SCM.new(buildable["scm"], buildable["uri"], buildable["branch"])
|
40
|
+
end
|
52
41
|
end
|
53
42
|
end
|
@@ -2,8 +2,8 @@ module Bob
|
|
2
2
|
# Different engines to run code in background. An engine is any object
|
3
3
|
# that responds to #call and takes a Proc object, which should be executed
|
4
4
|
# "in the background". The different engines are:
|
5
|
-
module
|
6
|
-
autoload :
|
7
|
-
|
5
|
+
module Engine
|
6
|
+
autoload :Threaded, "bob/engine/threaded"
|
7
|
+
Foreground = lambda {|b| b.call }
|
8
8
|
end
|
9
9
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Bob
|
4
|
+
module Engine
|
5
|
+
# A thread pool based build engine. This engine simply adds jobs to an
|
6
|
+
# in-memory queue, and processes them as soon as possible.
|
7
|
+
class Threaded
|
8
|
+
# The optional pool size controls how many threads will be created.
|
9
|
+
def initialize(pool_size = 2, logger = Bob.logger)
|
10
|
+
@pool = ThreadPool.new(pool_size, logger)
|
11
|
+
@logger = logger
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds a job to the queue.
|
15
|
+
def call(job)
|
16
|
+
@pool << job
|
17
|
+
end
|
18
|
+
|
19
|
+
# The number of jobs currently in the queue.
|
20
|
+
def njobs
|
21
|
+
@pool.njobs
|
22
|
+
end
|
23
|
+
|
24
|
+
# This method will not return until #njobs returns 0.
|
25
|
+
def wait!
|
26
|
+
Thread.pass until @pool.njobs == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Manage a pool of threads, allowing for spin up / spin down of the
|
30
|
+
# contained threads.
|
31
|
+
# Simply processes work added to it's queue via #push.
|
32
|
+
# The default size for the pool is 2 threads.
|
33
|
+
class ThreadPool
|
34
|
+
# A thread safe single value for use as a counter.
|
35
|
+
class Incrementor
|
36
|
+
# The value passed in will be the initial value.
|
37
|
+
def initialize(v = 0)
|
38
|
+
@m = Mutex.new
|
39
|
+
@v = v
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add the given value to self, default 1.
|
43
|
+
def inc(v = 1)
|
44
|
+
sync { @v += v }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Subtract the given value to self, default 1.
|
48
|
+
def dec(v = 1)
|
49
|
+
sync { @v -= v }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Simply shows the value inspect for convenience.
|
53
|
+
def inspect
|
54
|
+
@v.inspect
|
55
|
+
end
|
56
|
+
|
57
|
+
# Extract the value.
|
58
|
+
def to_i
|
59
|
+
@v
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Wrap the given block in a mutex.
|
65
|
+
def sync(&b)
|
66
|
+
@m.synchronize &b
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# The number of threads in the pool.
|
71
|
+
attr_reader :size
|
72
|
+
|
73
|
+
# The job queue.
|
74
|
+
attr_reader :jobs
|
75
|
+
|
76
|
+
# Set the size of the thread pool. Asynchronously run down threads
|
77
|
+
# that are no longer required, and synchronously spawn new required
|
78
|
+
# threads.
|
79
|
+
def size=(other)
|
80
|
+
@size = other
|
81
|
+
|
82
|
+
if @workers.size > @size
|
83
|
+
(@workers.size - @size).times do
|
84
|
+
@workers.shift[:run] = false
|
85
|
+
end
|
86
|
+
else
|
87
|
+
(@size - @workers.size).times do
|
88
|
+
@workers << spawn
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Default pool size is 2 threads.
|
94
|
+
def initialize(size = nil, logger = Bob.logger)
|
95
|
+
size ||= 2
|
96
|
+
@jobs = Queue.new
|
97
|
+
@njobs = Incrementor.new
|
98
|
+
@workers = Array.new(size) { spawn }
|
99
|
+
@logger = logger
|
100
|
+
end
|
101
|
+
|
102
|
+
# Adds a job to the queue, the job can be any number of objects
|
103
|
+
# responding to call, and/or a block.
|
104
|
+
def add(*jobs, &blk)
|
105
|
+
jobs = jobs + Array(blk)
|
106
|
+
|
107
|
+
jobs.each do |job|
|
108
|
+
@jobs << job
|
109
|
+
@njobs.inc
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :push, :add
|
114
|
+
alias_method :<<, :add
|
115
|
+
|
116
|
+
# A peak at the number of jobs in the queue. N.B. May differ, but
|
117
|
+
# should be more accurate than +jobs.size+.
|
118
|
+
def njobs
|
119
|
+
@njobs.to_i
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Create a new thread and return it. The thread will run until the
|
125
|
+
# thread-local value +:run+ is changed to false or nil.
|
126
|
+
def spawn
|
127
|
+
Thread.new do
|
128
|
+
c = Thread.current
|
129
|
+
c[:run] = true
|
130
|
+
|
131
|
+
while c[:run]
|
132
|
+
job = @jobs.pop
|
133
|
+
begin
|
134
|
+
job.call
|
135
|
+
rescue Exception => e
|
136
|
+
@logger.error("Exception occured during build: #{e.message}")
|
137
|
+
end
|
138
|
+
@njobs.dec
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|