rbuilder 0.0.1
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.
- 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
@@ -0,0 +1,33 @@
|
|
1
|
+
module Gui
|
2
|
+
class BuildListViewEntry
|
3
|
+
attr_reader :iterator, :build
|
4
|
+
def self.create(build, iterator)
|
5
|
+
entry = BuildListViewEntry.new(build)
|
6
|
+
entry.attach_to(iterator)
|
7
|
+
return entry
|
8
|
+
end
|
9
|
+
def initialize(build)
|
10
|
+
@build = build
|
11
|
+
build.add_observer(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def attach_to(iterator)
|
15
|
+
@iterator = iterator
|
16
|
+
update
|
17
|
+
end
|
18
|
+
|
19
|
+
def update
|
20
|
+
iterator[0] = self
|
21
|
+
iterator[1] = build.archive
|
22
|
+
iterator[2] = build.revision.to_s
|
23
|
+
iterator[3] = build.committer.to_s
|
24
|
+
iterator[4] = build.status.to_s
|
25
|
+
iterator[5] = build.summary.to_s
|
26
|
+
end
|
27
|
+
Colors = {BuildStatus.ok => 'green', BuildStatus.failed => 'red', BuildStatus.building => 'yellow'}
|
28
|
+
def update_renderer_style(renderer)
|
29
|
+
color = Colors[build.status]
|
30
|
+
renderer.background=color if color
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Gui
|
2
|
+
class BuilderListView
|
3
|
+
attr_reader :gtk_container,:tree_store, :tree_view
|
4
|
+
attr_accessor :monitor_window
|
5
|
+
|
6
|
+
def initialize(builder)
|
7
|
+
initialize_ui_elements
|
8
|
+
builder.each_description {|build_description| create_build_description_list_view(build_description)}
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_ui_elements
|
12
|
+
@tree_store = Gtk::TreeStore.new(BuildListViewEntry,String,String,String,String,String)
|
13
|
+
@tree_view = Gtk::TreeView.new(tree_store)
|
14
|
+
@gtk_container = Gtk::ScrolledWindow.new
|
15
|
+
gtk_container << tree_view
|
16
|
+
|
17
|
+
renderer = Gtk::CellRendererText.new
|
18
|
+
%w{Archive Revision Committer Status Summary}.each_with_index do |column, index|
|
19
|
+
col = Gtk::TreeViewColumn.new(column, renderer, :text => index+1)
|
20
|
+
col.resizable=true
|
21
|
+
col.set_cell_data_func(renderer) do |col, renderer, model, iter|
|
22
|
+
iter[0].update_renderer_style(renderer)
|
23
|
+
end
|
24
|
+
|
25
|
+
tree_view.append_column(col)
|
26
|
+
end
|
27
|
+
tree_view.selection.set_select_function do |selection, model, path, currently_selected|
|
28
|
+
self.select_build(self.build_for_iterator(model.get_iter(path)))
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_build_description_list_view(build_description)
|
34
|
+
BuildDescriptionListView.new(build_description, self, tree_store)
|
35
|
+
end
|
36
|
+
|
37
|
+
def select_build(build)
|
38
|
+
@selected_build = build
|
39
|
+
monitor_window.build_selected(build)
|
40
|
+
end
|
41
|
+
|
42
|
+
def replace_build_selection(new_selected_build, build_selection_to_replace)
|
43
|
+
select_build(new_selected_build) if build_selection_to_replace == selected_build || selected_build == nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_for_iterator(iterator)
|
47
|
+
iterator[0].build
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
attr_reader :selected_build
|
52
|
+
end
|
53
|
+
|
54
|
+
class BuildDescriptionListView
|
55
|
+
attr_reader :tree_store, :current_build_view_entry, :build_description, :builder_list_view
|
56
|
+
def initialize(builder, builder_list_view, tree_store = nil)
|
57
|
+
@listeners = []
|
58
|
+
@build_description = @builder = builder
|
59
|
+
@builder_list_view = builder_list_view
|
60
|
+
@builder.add_observer(self)
|
61
|
+
@tree_store = tree_store
|
62
|
+
end
|
63
|
+
|
64
|
+
def update(options = {})
|
65
|
+
replace_build_selection(options[:new_build])
|
66
|
+
new_build_list_view_entry(options[:new_build])
|
67
|
+
end
|
68
|
+
|
69
|
+
def replace_build_selection(new_build)
|
70
|
+
return unless builder_list_view
|
71
|
+
builder_list_view.replace_build_selection(new_build, current_build)
|
72
|
+
end
|
73
|
+
|
74
|
+
def current_build
|
75
|
+
return current_build_view_entry.build if current_build_view_entry
|
76
|
+
end
|
77
|
+
|
78
|
+
def new_build_list_view_entry(build)
|
79
|
+
if current_build_view_entry
|
80
|
+
current_build_view_entry
|
81
|
+
new_build_list_entry = BuildListViewEntry.create(build,current_build_view_entry.iterator)
|
82
|
+
current_build_view_entry.attach_to(tree_store.prepend(current_build_view_entry.iterator))
|
83
|
+
@current_build_view_entry = new_build_list_entry
|
84
|
+
else
|
85
|
+
@current_build_view_entry = BuildListViewEntry.create(build,tree_store.append(nil))
|
86
|
+
end
|
87
|
+
current_build_view_entry
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'gui/images'
|
2
|
+
module Gui
|
3
|
+
class BuilderMonitorWindow
|
4
|
+
|
5
|
+
class StatusIcon
|
6
|
+
attr_reader :status
|
7
|
+
def initialize(menu)
|
8
|
+
@icon = Gtk::StatusIcon.new
|
9
|
+
|
10
|
+
@icon.signal_connect('popup_menu', menu) do |widget,button, time|
|
11
|
+
if button == 3
|
12
|
+
menu.popup(nil,nil,button, time)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
self.ok
|
16
|
+
end
|
17
|
+
|
18
|
+
def ok
|
19
|
+
self.status ='ok_builder'
|
20
|
+
end
|
21
|
+
|
22
|
+
def broken
|
23
|
+
self.status ='broken_builder'
|
24
|
+
end
|
25
|
+
|
26
|
+
def status=(new_status)
|
27
|
+
@status = new_status
|
28
|
+
@icon.file=Images.file("#{status}.gif")
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_activate(&block)
|
33
|
+
@icon.signal_connect('activate',&block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
attr_reader :window, :status_icon, :builder
|
39
|
+
|
40
|
+
def initialize(builder, builder_list_view, build_details_pane)
|
41
|
+
@builder = builder
|
42
|
+
builder.add_observer(self)
|
43
|
+
@build_details_pane = build_details_pane
|
44
|
+
builder_list_view.monitor_window = self
|
45
|
+
initialize_widgets(builder_list_view)
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize_widgets(builder_list_view)
|
49
|
+
initialize_status_icon initialize_menu
|
50
|
+
@window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
|
51
|
+
panes = Gtk::VPaned.new
|
52
|
+
panes.pack1(builder_list_view.gtk_container,false, false)
|
53
|
+
panes.pack2(build_details_pane.gtk_container,false, false)
|
54
|
+
window.signal_connect("delete_event") { self.hide }
|
55
|
+
window.add(panes)
|
56
|
+
window.title = "rbuilder monitor"
|
57
|
+
window.width_request = 600
|
58
|
+
window.height_request = 500
|
59
|
+
window.icon_list = [Gdk::Pixbuf.new(Images.file('ok_builder.gif'))]
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize_menu
|
63
|
+
menu = Gtk::Menu.new
|
64
|
+
menu.append(show = Gtk::MenuItem.new("Show"))
|
65
|
+
menu.append(hide = Gtk::MenuItem.new("Hide"))
|
66
|
+
menu.append(exit = Gtk::MenuItem.new("Exit"))
|
67
|
+
menu.show_all
|
68
|
+
show.signal_connect('activate') {self.show}
|
69
|
+
hide.signal_connect('activate') {self.hide}
|
70
|
+
exit.signal_connect('activate') {Gtk.main_quit}
|
71
|
+
menu
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize_status_icon(menu)
|
75
|
+
@status_icon = StatusIcon.new(menu)
|
76
|
+
status_icon.on_activate do
|
77
|
+
toggle
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_selected(build)
|
82
|
+
@selected_build = build
|
83
|
+
@build_details_pane.attach_to build
|
84
|
+
end
|
85
|
+
|
86
|
+
def show
|
87
|
+
window.show_all
|
88
|
+
end
|
89
|
+
|
90
|
+
def hide
|
91
|
+
window.hide_all
|
92
|
+
end
|
93
|
+
|
94
|
+
def toggle
|
95
|
+
if window.visible?
|
96
|
+
window.hide_all
|
97
|
+
else
|
98
|
+
window.show_all
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def update
|
103
|
+
if builder.status == BuilderState.ok
|
104
|
+
status_icon.ok
|
105
|
+
else
|
106
|
+
status_icon.broken
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :build_details_pane
|
111
|
+
end
|
112
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/rbuilder/libs.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
class RevisionCommitted
|
3
|
+
attr_reader :committer, :revision
|
4
|
+
def initialize(revision, committer)
|
5
|
+
@revision = revision
|
6
|
+
@committer = committer
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_source_control_status(status)
|
10
|
+
self.new(status.revision, status.committer)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
return false unless other.class == self.class
|
15
|
+
self.revision == other.revision && self.committer == other.committer
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"Revision #{revision} committed by #{committer}"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SourceControl
|
2
|
+
class Status
|
3
|
+
attr_reader :revision, :committer
|
4
|
+
def initialize(revision, committer)
|
5
|
+
@revision = revision
|
6
|
+
@committer = committer
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
other.class == self.class &&
|
12
|
+
other.revision == self.revision &&
|
13
|
+
other.committer == self.committer
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"(#{revision},#{committer})"
|
18
|
+
end
|
19
|
+
def inspect
|
20
|
+
"Status#{to_s}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
module SourceControl
|
3
|
+
def self.svn(working_dir)
|
4
|
+
new(svn_backend(working_dir))
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.svn_backend(working_dir)
|
8
|
+
Svn.new(working_dir)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class Svn
|
13
|
+
attr_reader :working_dir
|
14
|
+
def initialize(working_dir)
|
15
|
+
@working_dir = working_dir
|
16
|
+
validate_working_dir
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_working_dir
|
20
|
+
unless File.directory?(working_dir)
|
21
|
+
raise DirectoryInvalidException.new("directory does not exist please create a valid checkout first")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def archive
|
26
|
+
return @archive if @archive
|
27
|
+
@archive = parse_archive(run_info)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_archive(repo_info)
|
31
|
+
repo_info =~ /^(Repository Root: )(.*)$/
|
32
|
+
line = $2
|
33
|
+
line = strip_server(line)
|
34
|
+
return strip_trunk(line)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_info
|
38
|
+
info_output = run_info
|
39
|
+
revision = info_output[/^Revision:(.*)$/].split(':')[1]
|
40
|
+
committer = info_output[/^Last Changed Author:(.*)$/].split(':')[1].strip
|
41
|
+
Status.new(revision.to_i, committer)
|
42
|
+
end
|
43
|
+
|
44
|
+
def strip_server(line)
|
45
|
+
return line unless line =~ /(svn.*:\/\/[^\/]*\/)(.*)/
|
46
|
+
return $2
|
47
|
+
end
|
48
|
+
|
49
|
+
def strip_trunk(line)
|
50
|
+
return line unless line =~ /(.*)(\/trunk)/
|
51
|
+
return $1
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
def run_info
|
56
|
+
FileUtils.cd @working_dir do
|
57
|
+
`svn up`
|
58
|
+
return `svn info`
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SourceControl
|
2
|
+
class DirectoryInvalidException < Exception; end
|
3
|
+
|
4
|
+
class Tracker
|
5
|
+
attr_reader :backend
|
6
|
+
|
7
|
+
def initialize(backend)
|
8
|
+
@backend = backend
|
9
|
+
@current_revision = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def report_change_to(builder)
|
13
|
+
source_control_info = backend.get_info
|
14
|
+
if source_control_info.revision != current_revision
|
15
|
+
builder.new_revision_committed(source_control_info)
|
16
|
+
@current_revision = source_control_info.revision
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def working_dir
|
21
|
+
backend.working_dir
|
22
|
+
end
|
23
|
+
|
24
|
+
def archive
|
25
|
+
backend.archive
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
attr_reader :current_revision
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.new(source_control_backend)
|
33
|
+
Tracker.new(source_control_backend)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'mocha'
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'builder_test_env')
|
5
|
+
require 'rbuilder/gui'
|
6
|
+
|
7
|
+
module Gui
|
8
|
+
class TestBuildDetailsPane < Test::Unit::TestCase
|
9
|
+
attr_reader :pane, :build, :build_report, :status_widget, :log_widget
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@build_report = stub('initial_build_report', :delete_observer => nil, :add_observer => nil, :log => '')
|
13
|
+
@build = stub_everything('initial_build')
|
14
|
+
build.stubs(:report).returns(build_report)
|
15
|
+
build.stubs(:archive).returns "the_archive"
|
16
|
+
build.stubs(:status).returns BuildStatus.ok
|
17
|
+
build.stubs(:reason).returns "some_reason"
|
18
|
+
@status_widget = stub('status_widget', :gtk_container => Gtk::Label.new, :status= => nil)
|
19
|
+
@log_widget = stub('log_widget', :attach_to => nil, :gtk_container => Gtk::Label.new)
|
20
|
+
@pane = BuildDetailsPane.new(build, status_widget, log_widget)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_contains_label_with_archive_name
|
24
|
+
assert_equal 'the_archive', pane.archive_label.text
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_contains_label_with_reason
|
28
|
+
assert_equal 'some_reason', pane.reason_label.text
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_observes_build_upon_creation
|
32
|
+
build.expects(:add_observer).with(instance_of(BuildDetailsPane))
|
33
|
+
pane = BuildDetailsPane.new(build)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_observes_build_report_and_build_upon_attach_to
|
37
|
+
build.expects(:delete_observer).with(pane)
|
38
|
+
other_build_report = mock('other_build_report')
|
39
|
+
other_build = stub('other_build', :report => other_build_report, :archive => 'other_archive', :reason => 'some_other_reason')
|
40
|
+
|
41
|
+
other_build.expects(:add_observer).with(pane)
|
42
|
+
log_widget.expects(:attach_to).with(other_build_report)
|
43
|
+
pane.expects :update
|
44
|
+
pane.attach_to(other_build)
|
45
|
+
assert_equal 'other_archive', pane.archive_label.text
|
46
|
+
assert_equal 'some_other_reason', pane.reason_label.text
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def test_update_pushes_status_information_to_attached_widgets
|
51
|
+
status_widget.expects(:status=).with(BuildStatus.ok)
|
52
|
+
pane.update
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
class TestStatusWidget < Test::Unit::TestCase
|
58
|
+
attr_reader :widget
|
59
|
+
def setup
|
60
|
+
@widget = StatusWidget.new(BuildStatus.ok)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_sets_status_in_image
|
64
|
+
assert_equal Gtk::Image, widget.gtk_container.class
|
65
|
+
assert_equal "ok.gif", File.basename(widget.gtk_container.file)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_changing_status_changes_the_file_not_the_label
|
69
|
+
expected_gtk_container = widget.gtk_container
|
70
|
+
|
71
|
+
widget.status = BuildStatus.failed
|
72
|
+
assert_equal "failed.gif", File.basename(widget.gtk_container.file)
|
73
|
+
assert_equal expected_gtk_container, widget.gtk_container
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class TestLogWidget < Test::Unit::TestCase
|
78
|
+
attr_reader :widget, :build_report
|
79
|
+
def setup
|
80
|
+
@build_report = stub_everything
|
81
|
+
@widget = LogWidget.new(build_report)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_obseves_build_erport_upon_creation
|
85
|
+
build_report.expects(:add_observer).with(instance_of(LogWidget))
|
86
|
+
widget = LogWidget.new(build_report)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_obseves_build_erport_upon_attach
|
90
|
+
new_build_report = mock
|
91
|
+
build_report.expects(:delete_observer).with(widget)
|
92
|
+
new_build_report.expects(:add_observer).with(widget)
|
93
|
+
widget.expects :update
|
94
|
+
widget.attach_to(new_build_report)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_update_log_line_is_added_to_text
|
98
|
+
new_log_line = "bla di bla"
|
99
|
+
another_log_line = "rob is gek"
|
100
|
+
|
101
|
+
widget.update(:log_line => new_log_line)
|
102
|
+
assert_match new_log_line, widget.text
|
103
|
+
assert_no_match(/#{another_log_line}/, widget.text)
|
104
|
+
|
105
|
+
widget.update(:log_line => another_log_line)
|
106
|
+
assert_match another_log_line, widget.text
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_update_sets_complete_text
|
110
|
+
the_log = "some\nlog\nlines"
|
111
|
+
build_report.stubs :log => the_log
|
112
|
+
widget.update
|
113
|
+
assert_equal the_log, widget.text
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|