rbuilder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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