rbuilder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +47 -0
- data/bin/rbuilder +65 -0
- data/conf/example.conf +2 -0
- data/rbuilder/build.rb +50 -0
- data/rbuilder/build_command.rb +33 -0
- data/rbuilder/build_configuration.rb +21 -0
- data/rbuilder/build_description.rb +46 -0
- data/rbuilder/build_report.rb +40 -0
- data/rbuilder/build_status.rb +39 -0
- data/rbuilder/builder.rb +45 -0
- data/rbuilder/gui.rb +6 -0
- data/rbuilder/gui/build_details_pane.rb +105 -0
- data/rbuilder/gui/build_list_view_entry.rb +33 -0
- data/rbuilder/gui/builder_list_view.rb +92 -0
- data/rbuilder/gui/builder_monitor_window.rb +112 -0
- data/rbuilder/gui/images.rb +5 -0
- data/rbuilder/gui/images/broken_builder.gif +0 -0
- data/rbuilder/gui/images/building.gif +0 -0
- data/rbuilder/gui/images/failed.gif +0 -0
- data/rbuilder/gui/images/ok.gif +0 -0
- data/rbuilder/gui/images/ok_builder.gif +0 -0
- data/rbuilder/lib/observer.rb +7 -0
- data/rbuilder/libs.rb +4 -0
- data/rbuilder/revision_committed.rb +21 -0
- data/rbuilder/source_control.rb +3 -0
- data/rbuilder/source_control/status.rb +23 -0
- data/rbuilder/source_control/svn.rb +64 -0
- data/rbuilder/source_control/tracker.rb +36 -0
- data/tests/gui/test_build_details_pane.rb +118 -0
- data/tests/gui/test_builder_list_view.rb +202 -0
- data/tests/gui/test_builder_list_view_entry.rb +79 -0
- data/tests/gui/test_builder_monitor_window.rb +81 -0
- data/tests/source_control/test_source_control.rb +52 -0
- data/tests/source_control/test_svn.rb +60 -0
- data/tests/test_build.rb +111 -0
- data/tests/test_build_command.rb +57 -0
- data/tests/test_build_configuration.rb +31 -0
- data/tests/test_build_description.rb +106 -0
- data/tests/test_build_report.rb +56 -0
- data/tests/test_builder.rb +54 -0
- data/tests/test_revision_comitted.rb +30 -0
- metadata +104 -0
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
Gem::manage_gems
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
desc 'Running all unit tests'
|
6
|
+
task :default => :test
|
7
|
+
task :test do
|
8
|
+
test_files = ['tests/**/test*.rb', 'tests/test*.rb']
|
9
|
+
test_files.each do |file_set|
|
10
|
+
Dir[file_set].each do |testfile|
|
11
|
+
require testfile
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Measures test coverage'
|
17
|
+
task :coverage do
|
18
|
+
rm_f "coverage"
|
19
|
+
rm_f "coverage.data"
|
20
|
+
rcov = "rcov --aggregate coverage.data"
|
21
|
+
system("#{rcov} --html tests/test*.rb")
|
22
|
+
system("#{rcov} --html tests/**/test*.rb")
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
spec = Gem::Specification.new do |s|
|
27
|
+
s.name = "rbuilder"
|
28
|
+
s.version = "0.0.1"
|
29
|
+
s.author = "Rob Westgeest"
|
30
|
+
s.email = "rob.westgeest@gmail.com"
|
31
|
+
s.homepage = "http://notaresource.blogspot.com/rbuilder"
|
32
|
+
s.platform = Gem::Platform::RUBY
|
33
|
+
s.summary = "A Ruby auto build tool like build-o-matic for running on a desktop pc"
|
34
|
+
s.files = FileList["Rakefile", "conf/example.conf", "{bin}/*","{rbuilder}/**/*"].to_a
|
35
|
+
s.test_files = FileList["{tests}/test*.rb","{tests}/**/test_*.rb"].to_a
|
36
|
+
s.has_rdoc = true
|
37
|
+
s.require_path = 'rbuilder'
|
38
|
+
s.executables << 'rbuilder'
|
39
|
+
s.add_dependency("mocha", ">= 0.5.5")
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
43
|
+
rm_f "pkg"
|
44
|
+
pkg.need_tar = true
|
45
|
+
end
|
46
|
+
|
47
|
+
|
data/bin/rbuilder
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)),'..','rbuilder')
|
3
|
+
require 'libs'
|
4
|
+
require 'gui'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
USAGE_MESSAGE = %Q{
|
8
|
+
usage #{$0} <working_dir> <build_command>
|
9
|
+
|
10
|
+
This program is an automatic builder for any build environment
|
11
|
+
(ant, make, rake... the lot). It accepts parameters for the
|
12
|
+
working directory and the build command. It assumes that the
|
13
|
+
working directory is a check-out of the archive that you are
|
14
|
+
working on and that the environment is subversion. It uses
|
15
|
+
svn commandline commands to get information on the archive and to poll
|
16
|
+
wether anything has changed in the archive.
|
17
|
+
|
18
|
+
If the subversion archive has been changed, a build will start
|
19
|
+
and reported in the gui of this program. The build command will
|
20
|
+
be executed in the working directory (i.e. probably the root of
|
21
|
+
your archive where the build files are expected to reside).
|
22
|
+
Your command would probably just be something simple like 'rake'
|
23
|
+
or 'make' or 'ant' but can be anything.
|
24
|
+
|
25
|
+
The build command is expected to have an exit value of 0 when
|
26
|
+
the build is successfull and anything else when the build is
|
27
|
+
not successfull.
|
28
|
+
}
|
29
|
+
|
30
|
+
def usage
|
31
|
+
puts USAGE_MESSAGE
|
32
|
+
exit(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
configuration_file = nil
|
36
|
+
parser = OptionParser.new do |opts|
|
37
|
+
opts.banner = USAGE_MESSAGE
|
38
|
+
opts.on("-h", '--help', "show this message") { usage }
|
39
|
+
opts.on("-c", '--configfile file', "use configuration file ") { |file| configuration_file = file }
|
40
|
+
end
|
41
|
+
|
42
|
+
rest = parser.parse ARGV
|
43
|
+
usage unless configuration_file || (rest[0] && rest[1])
|
44
|
+
|
45
|
+
builder = Builder.new
|
46
|
+
build_configuration = BuildConfiguration.new(builder)
|
47
|
+
|
48
|
+
if configuration_file
|
49
|
+
build_configuration.from_file(configuration_file)
|
50
|
+
else
|
51
|
+
working_dir = rest[0]
|
52
|
+
build_command = rest[1]
|
53
|
+
build_configuration.build(working_dir, build_command)
|
54
|
+
end
|
55
|
+
|
56
|
+
list_view = Gui::BuilderListView.new(builder)
|
57
|
+
details_pane = Gui::BuildDetailsPane.new(Build.new(nil,nil,nil))
|
58
|
+
view = Gui::BuilderMonitorWindow.new(builder,list_view,details_pane)
|
59
|
+
view.show
|
60
|
+
|
61
|
+
Thread.start do
|
62
|
+
builder.run
|
63
|
+
end
|
64
|
+
Gtk.main
|
65
|
+
|
data/conf/example.conf
ADDED
data/rbuilder/build.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'lib/observer'
|
2
|
+
require 'build_report'
|
3
|
+
class Build
|
4
|
+
include Observable
|
5
|
+
|
6
|
+
attr_reader :reason, :report, :archive
|
7
|
+
|
8
|
+
def initialize(build_command, working_dir, reason, archive = '')
|
9
|
+
@build_command = build_command
|
10
|
+
@working_dir = working_dir
|
11
|
+
@reason = reason
|
12
|
+
@report = BuildReport.new
|
13
|
+
@archive = archive
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
self.building=true
|
18
|
+
report.info("running build with reason #{reason}\n")
|
19
|
+
build_command.execute(working_dir, report)
|
20
|
+
self.building=false
|
21
|
+
end
|
22
|
+
|
23
|
+
def status
|
24
|
+
return building && BuildStatus.building || report.status
|
25
|
+
end
|
26
|
+
|
27
|
+
def summary
|
28
|
+
return building && reason.to_s || report.summary
|
29
|
+
end
|
30
|
+
|
31
|
+
def building=(value)
|
32
|
+
@building=value
|
33
|
+
changed!
|
34
|
+
end
|
35
|
+
|
36
|
+
def revision
|
37
|
+
return reason.revision
|
38
|
+
end
|
39
|
+
|
40
|
+
def committer
|
41
|
+
return reason.committer
|
42
|
+
end
|
43
|
+
|
44
|
+
def green?
|
45
|
+
status == BuildStatus.ok
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
attr_reader :build_command, :working_dir, :building
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
class BuildCommand
|
3
|
+
include FileUtils
|
4
|
+
def initialize(commandline)
|
5
|
+
@commandline = commandline
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute(working_dir, build_result)
|
9
|
+
cd working_dir do
|
10
|
+
if run_command(build_result)
|
11
|
+
build_result.green
|
12
|
+
else
|
13
|
+
build_result.red
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_command(build_result)
|
19
|
+
puts "running '#{commandline}' in working dir '#{`pwd`.strip}'"
|
20
|
+
IO.popen commandline do |stdout|
|
21
|
+
while !stdout.eof?
|
22
|
+
line = stdout.readline
|
23
|
+
build_result.info(line)
|
24
|
+
puts line
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return $? == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
attr_reader :commandline
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'source_control'
|
2
|
+
require 'build_description'
|
3
|
+
require 'build_command'
|
4
|
+
|
5
|
+
class BuildConfiguration
|
6
|
+
def initialize(builder)
|
7
|
+
@builder = builder
|
8
|
+
end
|
9
|
+
|
10
|
+
def build(dir, command)
|
11
|
+
@builder.add_description BuildDescription.new(BuildCommand.new(command), SourceControl.svn(dir))
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_text(text)
|
15
|
+
instance_eval(text)
|
16
|
+
end
|
17
|
+
|
18
|
+
def from_file(file)
|
19
|
+
from_text(File.read(file))
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'build_status'
|
2
|
+
require 'revision_committed'
|
3
|
+
|
4
|
+
class BuildDescription
|
5
|
+
include Observable
|
6
|
+
attr_reader :builds
|
7
|
+
|
8
|
+
def initialize(build_command, source_control)
|
9
|
+
@build_command = build_command
|
10
|
+
@builds = []
|
11
|
+
@source_control = source_control
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
return BuilderState.ok if builds.empty? || builds.last.green?
|
16
|
+
return BuilderState.broken
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_build(reason)
|
20
|
+
build = create_build(reason)
|
21
|
+
changed!(:new_build => build)
|
22
|
+
build.run
|
23
|
+
builds << build
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_revision_committed(source_control_status)
|
27
|
+
run_build(RevisionCommitted.from_source_control_status(source_control_status))
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_build(reason)
|
31
|
+
Build.new(build_command, working_dir, reason, source_control.archive)
|
32
|
+
end
|
33
|
+
|
34
|
+
def iterate
|
35
|
+
source_control.report_change_to(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def working_dir
|
41
|
+
source_control.working_dir
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :build_command, :source_control
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'lib/observer'
|
2
|
+
class BuildReport
|
3
|
+
include Observable
|
4
|
+
attr_reader :status
|
5
|
+
|
6
|
+
def initialize(status = BuildStatus.ok)
|
7
|
+
@status = status
|
8
|
+
@loglines = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def green
|
12
|
+
self.status = BuildStatus.ok
|
13
|
+
end
|
14
|
+
|
15
|
+
def red
|
16
|
+
self.status = BuildStatus.failed
|
17
|
+
end
|
18
|
+
|
19
|
+
def status=(new_value)
|
20
|
+
@status = new_value
|
21
|
+
changed!
|
22
|
+
end
|
23
|
+
|
24
|
+
def info(line_of_output)
|
25
|
+
line_of_output = line_of_output.strip
|
26
|
+
loglines << line_of_output
|
27
|
+
changed! :log_line => line_of_output
|
28
|
+
end
|
29
|
+
|
30
|
+
def summary
|
31
|
+
loglines.last || ''
|
32
|
+
end
|
33
|
+
|
34
|
+
def log
|
35
|
+
loglines.join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
attr_reader :loglines
|
40
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class BuilderState
|
2
|
+
def initialize(value)
|
3
|
+
@value = value
|
4
|
+
end
|
5
|
+
@@broken = BuilderState.new('broken')
|
6
|
+
@@ok = BuilderState.new('ok')
|
7
|
+
|
8
|
+
def self.broken
|
9
|
+
return @@broken
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.ok
|
13
|
+
return @@ok
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
@value
|
18
|
+
end
|
19
|
+
alias_method :inspect, :to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
class BuildStatus
|
23
|
+
def initialize(value)
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
@@ok = BuildStatus.new('ok')
|
27
|
+
@@failed = BuildStatus.new('failed')
|
28
|
+
@@building = BuildStatus.new('building')
|
29
|
+
|
30
|
+
def self.ok; return @@ok; end
|
31
|
+
def self.failed; return @@failed; end
|
32
|
+
def self.building; return @@building; end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@value
|
36
|
+
end
|
37
|
+
alias_method :inspect, :to_s
|
38
|
+
end
|
39
|
+
|
data/rbuilder/builder.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'lib/observer'
|
2
|
+
require 'build_description'
|
3
|
+
|
4
|
+
class Builder
|
5
|
+
include Observable
|
6
|
+
|
7
|
+
attr_reader :descriptions
|
8
|
+
def initialize
|
9
|
+
@descriptions = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_description(build_description)
|
13
|
+
descriptions << build_description
|
14
|
+
end
|
15
|
+
|
16
|
+
def each_description(&block)
|
17
|
+
descriptions.each(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def iterate
|
21
|
+
descriptions.each {|description| iterate_description(description)}
|
22
|
+
end
|
23
|
+
|
24
|
+
def iterate_description(desc)
|
25
|
+
desc.iterate
|
26
|
+
changed!
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
while true
|
31
|
+
begin
|
32
|
+
iterate
|
33
|
+
rescue Exception => e
|
34
|
+
puts e.to_s
|
35
|
+
end
|
36
|
+
sleep(5)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def status
|
41
|
+
descriptions.each {|description| return BuilderState.broken if description.status == BuilderState.broken}
|
42
|
+
return BuilderState.ok
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
data/rbuilder/gui.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'gui/images'
|
2
|
+
module Gui
|
3
|
+
class LogWidget
|
4
|
+
attr_reader :gtk_container
|
5
|
+
def initialize(build_report)
|
6
|
+
@gtk_container = Gtk::Frame.new "Build log"
|
7
|
+
gtk_container.shadow_type = Gtk::SHADOW_IN
|
8
|
+
|
9
|
+
gtk_container << window = Gtk::ScrolledWindow.new
|
10
|
+
window << @text_view = Gtk::TextView.new
|
11
|
+
text_view.editable = false
|
12
|
+
@build_report = build_report
|
13
|
+
build_report.add_observer(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def attach_to(new_build_report)
|
17
|
+
build_report.delete_observer(self)
|
18
|
+
@build_report = new_build_report
|
19
|
+
build_report.add_observer(self)
|
20
|
+
update
|
21
|
+
end
|
22
|
+
|
23
|
+
def update(options = {})
|
24
|
+
update_text(options[:log_line]) and return if options.has_key?(:log_line)
|
25
|
+
set_text(build_report.log)
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_text(line)
|
29
|
+
text_view.buffer.insert(text_view.buffer.end_iter, line + $/)
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_text(new_text)
|
33
|
+
text_view.buffer.text=new_text
|
34
|
+
end
|
35
|
+
|
36
|
+
def text
|
37
|
+
text_view.buffer.text
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
attr_reader :build_report, :text_view
|
42
|
+
end
|
43
|
+
|
44
|
+
class StatusWidget
|
45
|
+
Image = {BuildStatus.ok => Images.file('ok.gif'), BuildStatus.failed => Images.file('failed.gif'), BuildStatus.building => Images.file('building.gif')}
|
46
|
+
Image.default = ''
|
47
|
+
attr_reader :gtk_container
|
48
|
+
|
49
|
+
def initialize(build_status)
|
50
|
+
@gtk_container = Gtk::Image.new(Image[build_status])
|
51
|
+
end
|
52
|
+
|
53
|
+
def status=(new_status)
|
54
|
+
@gtk_container.file=Image[new_status]
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class BuildDetailsPane
|
60
|
+
attr_reader :build, :gtk_container
|
61
|
+
attr_reader :archive_label, :reason_label
|
62
|
+
def initialize(build, status_widget = nil, log_widget = nil)
|
63
|
+
@status_widget = status_widget || StatusWidget.new(build.status)
|
64
|
+
@log_widget = log_widget || LogWidget.new(build.report)
|
65
|
+
@archive_label = Gtk::Label.new
|
66
|
+
@reason_label = Gtk::Label.new
|
67
|
+
|
68
|
+
@gtk_container = Gtk::VBox.new
|
69
|
+
add_label("builing archive:", archive_label)
|
70
|
+
add_label(" with reason:", reason_label)
|
71
|
+
@gtk_container.pack_start(@status_widget.gtk_container, false, false, 5)
|
72
|
+
@gtk_container.pack_start(@log_widget.gtk_container, true, true, 5)
|
73
|
+
|
74
|
+
attach_to(build)
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_label(label_text, widget)
|
79
|
+
box = Gtk::HBox.new
|
80
|
+
label = Gtk::Label.new(label_text)
|
81
|
+
box.pack_start(label, false, false, 10)
|
82
|
+
box.pack_start(widget, false, false, 5)
|
83
|
+
gtk_container.pack_start(box, false, false, 5)
|
84
|
+
end
|
85
|
+
|
86
|
+
def attach_to(new_build)
|
87
|
+
if (build)
|
88
|
+
build.delete_observer(self)
|
89
|
+
end
|
90
|
+
@build = new_build
|
91
|
+
build.add_observer(self)
|
92
|
+
log_widget.attach_to(build.report)
|
93
|
+
archive_label.text = build.archive
|
94
|
+
reason_label.text = build.reason.to_s
|
95
|
+
update
|
96
|
+
end
|
97
|
+
|
98
|
+
def update
|
99
|
+
status_widget.status = build.status
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
attr_reader :status_widget, :reason_widget, :log_widget
|
104
|
+
end
|
105
|
+
end
|