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.
Files changed (42) hide show
  1. data/Rakefile +47 -0
  2. data/bin/rbuilder +65 -0
  3. data/conf/example.conf +2 -0
  4. data/rbuilder/build.rb +50 -0
  5. data/rbuilder/build_command.rb +33 -0
  6. data/rbuilder/build_configuration.rb +21 -0
  7. data/rbuilder/build_description.rb +46 -0
  8. data/rbuilder/build_report.rb +40 -0
  9. data/rbuilder/build_status.rb +39 -0
  10. data/rbuilder/builder.rb +45 -0
  11. data/rbuilder/gui.rb +6 -0
  12. data/rbuilder/gui/build_details_pane.rb +105 -0
  13. data/rbuilder/gui/build_list_view_entry.rb +33 -0
  14. data/rbuilder/gui/builder_list_view.rb +92 -0
  15. data/rbuilder/gui/builder_monitor_window.rb +112 -0
  16. data/rbuilder/gui/images.rb +5 -0
  17. data/rbuilder/gui/images/broken_builder.gif +0 -0
  18. data/rbuilder/gui/images/building.gif +0 -0
  19. data/rbuilder/gui/images/failed.gif +0 -0
  20. data/rbuilder/gui/images/ok.gif +0 -0
  21. data/rbuilder/gui/images/ok_builder.gif +0 -0
  22. data/rbuilder/lib/observer.rb +7 -0
  23. data/rbuilder/libs.rb +4 -0
  24. data/rbuilder/revision_committed.rb +21 -0
  25. data/rbuilder/source_control.rb +3 -0
  26. data/rbuilder/source_control/status.rb +23 -0
  27. data/rbuilder/source_control/svn.rb +64 -0
  28. data/rbuilder/source_control/tracker.rb +36 -0
  29. data/tests/gui/test_build_details_pane.rb +118 -0
  30. data/tests/gui/test_builder_list_view.rb +202 -0
  31. data/tests/gui/test_builder_list_view_entry.rb +79 -0
  32. data/tests/gui/test_builder_monitor_window.rb +81 -0
  33. data/tests/source_control/test_source_control.rb +52 -0
  34. data/tests/source_control/test_svn.rb +60 -0
  35. data/tests/test_build.rb +111 -0
  36. data/tests/test_build_command.rb +57 -0
  37. data/tests/test_build_configuration.rb +31 -0
  38. data/tests/test_build_description.rb +106 -0
  39. data/tests/test_build_report.rb +56 -0
  40. data/tests/test_builder.rb +54 -0
  41. data/tests/test_revision_comitted.rb +30 -0
  42. metadata +104 -0
@@ -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
+
@@ -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
+
@@ -0,0 +1,2 @@
1
+ build '../../integration_tests/some_project', 'rake'
2
+ build '../../integration_tests/rbuilder', 'rake'
@@ -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
+
@@ -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
+
@@ -0,0 +1,6 @@
1
+ require 'gtk2'
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'libs')
3
+ require 'gui/build_list_view_entry'
4
+ require 'gui/builder_list_view'
5
+ require 'gui/build_details_pane'
6
+ require 'gui/builder_monitor_window'
@@ -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