BuildMaster 0.5.0
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/README +25 -0
- data/lib/buildmaster.rb +9 -0
- data/lib/buildmaster/ant_client.rb +132 -0
- data/lib/buildmaster/build_file.rb +11 -0
- data/lib/buildmaster/cvs_client.rb +57 -0
- data/lib/buildmaster/file_processor.rb +62 -0
- data/lib/buildmaster/release_control.rb +19 -0
- data/lib/buildmaster/run_ant.rb +31 -0
- data/lib/buildmaster/shell_command.rb +38 -0
- data/lib/buildmaster/site.rb +147 -0
- data/lib/buildmaster/source_file_handler.rb +68 -0
- data/lib/buildmaster/svn_driver.rb +68 -0
- data/lib/buildmaster/template_runner.rb +128 -0
- data/lib/buildmaster/try.rb +3 -0
- data/lib/buildmaster/xtemplate.rb +28 -0
- data/lib/mock.rb +3 -0
- data/lib/mock/mock_base.rb +24 -0
- data/test/buildmaster/build.xml +8 -0
- data/test/buildmaster/content/index.html +7 -0
- data/test/buildmaster/tc_ant_client.rb +27 -0
- data/test/buildmaster/tc_cvs_client.rb +62 -0
- data/test/buildmaster/tc_release_control.rb +23 -0
- data/test/buildmaster/tc_site.rb +58 -0
- data/test/buildmaster/tc_svn_driver.rb +74 -0
- data/test/buildmaster/tc_template_runner.rb +48 -0
- data/test/buildmaster/tc_xtemplate.rb +256 -0
- data/test/buildmaster/template.xhtml +10 -0
- data/test/ts_buildmaster.rb +10 -0
- metadata +81 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'webrick'
|
4
|
+
require 'file_processor'
|
5
|
+
|
6
|
+
module BuildMaster
|
7
|
+
|
8
|
+
class SourceFileHandler < WEBrick::HTTPServlet::AbstractServlet
|
9
|
+
# uncomment the following for automatic servlet reloading
|
10
|
+
def SourceFileHandler.get_instance config, *options
|
11
|
+
load __FILE__
|
12
|
+
SourceFileHandler.new config, *options
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(server, spec)
|
16
|
+
super
|
17
|
+
@config = server.config
|
18
|
+
@logger = @config[:Logger]
|
19
|
+
@spec = spec
|
20
|
+
@delegate = WEBrick::HTTPServlet::FileHandler.new(server, spec.content_dir, true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def service(req, res)
|
24
|
+
path = req.path_info
|
25
|
+
extension = File.extname(path)
|
26
|
+
if (extension == '')
|
27
|
+
path = "#{path}index.html"
|
28
|
+
extension = '.html'
|
29
|
+
end
|
30
|
+
if (extension.casecmp('.html') == 0)
|
31
|
+
begin
|
32
|
+
serve_generated_file(path[0, path.length - 5], req, res)
|
33
|
+
rescue Exception
|
34
|
+
@logger.error("error serving the file #{path}")
|
35
|
+
@logger.error($!)
|
36
|
+
raise WEBrick::HTTPStatus::InternalServerError,
|
37
|
+
"#{$!}", caller
|
38
|
+
end
|
39
|
+
else
|
40
|
+
@delegate.service(req, res)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def serve_generated_file(path, req, res)
|
46
|
+
file_path = @spec.content_dir + path
|
47
|
+
textile_file = "#{file_path}.textile"
|
48
|
+
html_file = "#{file_path}.html"
|
49
|
+
if File.file? textile_file
|
50
|
+
stats = File::stat(textile_file)
|
51
|
+
document = FileProcessor.new(@spec.load_template, textile_file, @spec).process_textile()
|
52
|
+
elsif File.file? html_file
|
53
|
+
stats = File::stat(html_file)
|
54
|
+
document = FileProcessor.new(@spec.load_template, html_file, @spec).process_html()
|
55
|
+
end
|
56
|
+
if (document)
|
57
|
+
content = document.to_s
|
58
|
+
res['content-type'] = 'text/html'
|
59
|
+
res['content-length'] = content.length
|
60
|
+
res['last-modified'] = stats.mtime.httpdate
|
61
|
+
res.body = content
|
62
|
+
else
|
63
|
+
@delegate.service(req, res)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module BuildMaster
|
4
|
+
|
5
|
+
class SvnInfo
|
6
|
+
attr_reader :path, :repository_root
|
7
|
+
|
8
|
+
def initialize(path)
|
9
|
+
@path = path
|
10
|
+
analyze_entry_file(File.join(path, ".svn", "entries"))
|
11
|
+
end
|
12
|
+
|
13
|
+
def analyze_entry_file(file)
|
14
|
+
xml = REXML::Document.new(File.open(file))
|
15
|
+
xml.root.each_element_with_attribute('name', '', 1) do |element|
|
16
|
+
@repository_root = element.attributes['repos']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class SvnDriver
|
22
|
+
include Shell
|
23
|
+
|
24
|
+
def SvnDriver::from_path(directory)
|
25
|
+
return SvnDriver.new(SvnInfo.new(directory))
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(svn_info, &command_runner)
|
29
|
+
@svn_info = svn_info
|
30
|
+
if (command_runner)
|
31
|
+
@command_runner = command_runner
|
32
|
+
else
|
33
|
+
@command_runner = Proc.new {|command| run(command)}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def status
|
38
|
+
command_for_path('status')
|
39
|
+
end
|
40
|
+
|
41
|
+
def update
|
42
|
+
command_for_path('update')
|
43
|
+
end
|
44
|
+
|
45
|
+
def commit(comment)
|
46
|
+
command_for_path('commit', " -m \"#{comment}\"")
|
47
|
+
end
|
48
|
+
|
49
|
+
def tag(tag_name)
|
50
|
+
run_command("svn copy #{@svn_info.repository_root}/trunk #{@svn_info.repository_root}/tags/#{tag_name} -m \"ruby buildmaster\"")
|
51
|
+
end
|
52
|
+
|
53
|
+
def checkout(output)
|
54
|
+
run_command("svn checkout #{@svn_info.repository_root}/trunk #{output}")
|
55
|
+
end
|
56
|
+
|
57
|
+
def command(command)
|
58
|
+
command_for_path(command)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def command_for_path(svn_command, argument='')
|
64
|
+
run_command("svn #{svn_command} #{@svn_info.path}#{argument}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module BuildMaster
|
2
|
+
class TemplateRunner
|
3
|
+
NAMESPACE = "http://buildmaster.rubyforge.org/xtemplate/1.0"
|
4
|
+
|
5
|
+
def initialize(result, template, source, &evaluator)
|
6
|
+
@result = result
|
7
|
+
@template = template
|
8
|
+
@source = source
|
9
|
+
@evaluator = evaluator
|
10
|
+
end
|
11
|
+
|
12
|
+
def process
|
13
|
+
process_children(@result, @template)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def process_children(target, template)
|
18
|
+
template.each do |element|
|
19
|
+
if (element.kind_of? REXML::Element)
|
20
|
+
process_element(target, element)
|
21
|
+
else
|
22
|
+
target.add(deep_clone(element))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_element(target, element)
|
28
|
+
if (template_directive?(element))
|
29
|
+
process_directive(target, element)
|
30
|
+
else
|
31
|
+
output_element = clone_element(element)
|
32
|
+
target.add(output_element)
|
33
|
+
process_children(output_element, element)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def template_directive?(element)
|
38
|
+
element.namespace == NAMESPACE
|
39
|
+
end
|
40
|
+
|
41
|
+
def process_directive(target, template_element)
|
42
|
+
begin
|
43
|
+
action = method("process_#{template_element.name}_directive")
|
44
|
+
action.call(target, template_element)
|
45
|
+
rescue NameError
|
46
|
+
raise TemplateException,
|
47
|
+
"unable to process template:#{template_element.name}: #{$!}" ,
|
48
|
+
caller
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def clone_element( template_element )
|
53
|
+
cloned_element = REXML::Element.new( template_element.expanded_name )
|
54
|
+
copy_attributes( template_element, cloned_element )
|
55
|
+
cloned_element
|
56
|
+
end
|
57
|
+
|
58
|
+
def copy_attributes( template_element, expanded_element )
|
59
|
+
template_element.attributes.each_attribute do |attribute|
|
60
|
+
expanded_element.attributes.add( attribute.clone )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def deep_clone( node )
|
65
|
+
if node.kind_of? REXML::Parent
|
66
|
+
node.deep_clone
|
67
|
+
else
|
68
|
+
node.clone
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_include_directive(target_element, template_element)
|
73
|
+
elements_attribute = template_element.attributes["elements"]
|
74
|
+
if (elements_attribute)
|
75
|
+
REXML::XPath.match(@source, elements_attribute).each do |matched|
|
76
|
+
target_element.add(deep_clone(matched))
|
77
|
+
end
|
78
|
+
return
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_when_directive(target_element, template_element)
|
83
|
+
if evaluate(template_element, 'test')
|
84
|
+
process_children(target_element, template_element)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_attribute_directive(target_element, template_element)
|
89
|
+
name = template_element.attributes['name']
|
90
|
+
target_element.attributes[name]=evaluate(template_element, 'eval')
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_text_directive(target_element, template_element)
|
94
|
+
target_element.add(REXML::Text.new(evaluate(template_element, 'eval')))
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_each_directive(target_element, template_element)
|
98
|
+
source_xml = evaluate!(template_element, 'source')
|
99
|
+
matched_element = REXML::XPath.match(REXML::Document.new(source_xml), template_element.attributes['select'])
|
100
|
+
count = attribute!(template_element, 'count')
|
101
|
+
count.to_i.times do |i|
|
102
|
+
TemplateRunner.new(target_element, template_element, matched_element[i], &@evaluator).process
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def evaluate!(xml_element, attribute_name)
|
107
|
+
value = evaluate(xml_element, attribute_name)
|
108
|
+
message = xml_element.attributes[attribute_name]
|
109
|
+
if (not value)
|
110
|
+
raise TemplateException, "#{message} from attribute #{attribute_name} cannot be nil"
|
111
|
+
end
|
112
|
+
return value
|
113
|
+
end
|
114
|
+
|
115
|
+
def evaluate(xml_element, attribute_name)
|
116
|
+
message = attribute!(xml_element, attribute_name)
|
117
|
+
return @evaluator.call(message)
|
118
|
+
end
|
119
|
+
|
120
|
+
def attribute!(xml_element, attribute_name)
|
121
|
+
value = xml_element.attributes[attribute_name]
|
122
|
+
if (not value)
|
123
|
+
raise TemplateException, "attribute #{attribute_name} not found in #{xml_element}"
|
124
|
+
end
|
125
|
+
return value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'template_runner'
|
5
|
+
|
6
|
+
module BuildMaster
|
7
|
+
class TemplateException < Exception
|
8
|
+
end
|
9
|
+
|
10
|
+
class XTemplate
|
11
|
+
def initialize(template_file)
|
12
|
+
@template = REXML::Document.new(template_file)
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(content, &evaluator)
|
16
|
+
content_xml = REXML::Document.new(content)
|
17
|
+
output_xml = process_xml(content_xml, &evaluator)
|
18
|
+
return output_xml
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def process_xml(content_xml, &evaluator)
|
23
|
+
result_xml = REXML::Document.new
|
24
|
+
TemplateRunner.new(result_xml, @template, content_xml, &evaluator).process
|
25
|
+
return result_xml
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/mock.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class Mock
|
5
|
+
def initialize(class_to_mock=nil)
|
6
|
+
@class_to_mock = class_to_mock
|
7
|
+
@expectations = Set.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def expects(message)
|
11
|
+
@expectations.add(message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args)
|
19
|
+
if (not @expectations.include? method)
|
20
|
+
raise "Unexpected extra call to #{method}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "..", "lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'buildmaster'
|
5
|
+
|
6
|
+
class AntTest < Test::Unit::TestCase
|
7
|
+
protected
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
build_file = File.join(File.dirname(__FILE__), "build.xml")
|
11
|
+
@ant = Ant.new(build_file)
|
12
|
+
end
|
13
|
+
|
14
|
+
public
|
15
|
+
|
16
|
+
def test_run
|
17
|
+
@ant.project_help
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_pass
|
21
|
+
@ant.target('passing')
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_fail
|
25
|
+
assert_raise(RuntimeError) {@ant.target('failing')}
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "..", "lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'buildmaster'
|
5
|
+
|
6
|
+
class CvsClientTest < Test::Unit::TestCase
|
7
|
+
protected
|
8
|
+
def setUp()
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def ensure_folder_exists(path)
|
14
|
+
if (not File.exists? path)
|
15
|
+
Dir.mkdir(path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
public
|
20
|
+
def test_load_CvsInfo
|
21
|
+
folder = 'tmp'
|
22
|
+
ensure_folder_exists(folder);
|
23
|
+
root = ':ext:wolfdancer@cvsserver.com:/cvs/root'
|
24
|
+
repository = 'xpe'
|
25
|
+
write("#{folder}/ROOT", root)
|
26
|
+
write("#{folder}/Repository", repository)
|
27
|
+
cvs = CvsInfo.load(folder)
|
28
|
+
assert_equal(root, cvs.root)
|
29
|
+
assert_equal(repository, cvs.repository)
|
30
|
+
end
|
31
|
+
|
32
|
+
def tes_checkout
|
33
|
+
log = ''
|
34
|
+
client = CvsClient.new(CvsInfo.new('root', 'module'), 'working') {|command| log = command}
|
35
|
+
client.checkout
|
36
|
+
assert_equal('cvs -d root co -d working module', log)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_update
|
40
|
+
log = ''
|
41
|
+
client = CvsClient.new(CvsInfo.new('root', 'module'), 'working') {|command| log = command}
|
42
|
+
client.update
|
43
|
+
assert_equal('cvs -d root update working', log)
|
44
|
+
client.update('-PAd')
|
45
|
+
assert_equal('cvs -d root update -PAd working', log)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_command
|
49
|
+
log = ''
|
50
|
+
client = CvsClient.new(CvsInfo.new('root', 'module'), 'working') {|command| log = command}
|
51
|
+
client.command('command -option argument')
|
52
|
+
assert_equal('cvs -d root command -option argument working', log)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def write(fileName, content)
|
57
|
+
File.open(fileName, "w") do |file|
|
58
|
+
file.puts content
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "..", "lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'buildmaster'
|
5
|
+
require 'mock'
|
6
|
+
|
7
|
+
class ReleaseTest < Test::Unit::TestCase
|
8
|
+
def testRelease
|
9
|
+
cvs_mock = Mock.new(CvsClient)
|
10
|
+
cvs_mock.expects(:checkout)
|
11
|
+
cvs_mock.expects(:tag)
|
12
|
+
cvs_mock.expects(:commit)
|
13
|
+
|
14
|
+
builder_mock = Mock.new
|
15
|
+
builder_mock.expects(:build)
|
16
|
+
|
17
|
+
release = Release.new(cvs_mock, builder_mock)
|
18
|
+
release.release_candidate('tag')
|
19
|
+
|
20
|
+
cvs_mock.verify
|
21
|
+
builder_mock.verify
|
22
|
+
end
|
23
|
+
end
|