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
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
|