jeroenvandijk-bob 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.rdoc +30 -0
- data/Rakefile +70 -0
- data/VERSION +1 -0
- data/lib/bob.rb +35 -0
- data/lib/bob/buildable.rb +74 -0
- data/lib/bob/builder.rb +53 -0
- data/lib/bob/scm.rb +25 -0
- data/lib/bob/scm/abstract.rb +65 -0
- data/lib/bob/scm/git.rb +34 -0
- data/lib/bob/scm/svn.rb +37 -0
- data/lib/bob/test.rb +4 -0
- data/lib/bob/test/buildable_stub.rb +43 -0
- data/lib/bob/test/scm/abstract.rb +62 -0
- data/lib/bob/test/scm/git.rb +48 -0
- data/lib/bob/test/scm/svn.rb +67 -0
- data/test/bob_test.rb +16 -0
- data/test/engine/threaded_test.rb +52 -0
- data/test/helper.rb +30 -0
- data/test/scm/git_test.rb +66 -0
- data/test/scm/svn_test.rb +66 -0
- data/test/test_test.rb +62 -0
- metadata +135 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2008-2009 Nicolas Sanguinetti, entp.com
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
= Bob the Builder
|
2
|
+
|
3
|
+
Given a Buildable object with a determined API (described in it's documentation),
|
4
|
+
Bob will, when called like:
|
5
|
+
|
6
|
+
Bob.build(buildable, commit_id) # or Bob.build(buildable, [commit_id, commit_id, ...])
|
7
|
+
|
8
|
+
or from your buildable (if you are using the Bob::Buildable mixin provided) as:
|
9
|
+
|
10
|
+
buildable.build(commit_id) # or buildable.build([commit_id, commit_id, ...])
|
11
|
+
|
12
|
+
1. Checkout the buildable on the specified commit
|
13
|
+
2. Call <tt>Buildable#start_building</tt>
|
14
|
+
3. Run the script provided in <tt>Buildable#build_script</tt> in the buildable.
|
15
|
+
4. When the build process finishes, it will call <tt>Buildable#finish_building</tt> with
|
16
|
+
the commit_id, the build status (true if the script returns a status code
|
17
|
+
of 0, false otherwise), and a string with the build output (both STDOUT and STDERR).
|
18
|
+
|
19
|
+
If you pass an array of commits, the steps 1-4 will be repeated for each commit provided,
|
20
|
+
in order.
|
21
|
+
|
22
|
+
== Do I need this?
|
23
|
+
|
24
|
+
Probably not. Check out integrity[http://integrityapp.com] for a full fledged
|
25
|
+
automated CI server, which is what most people need.
|
26
|
+
|
27
|
+
== Credits
|
28
|
+
|
29
|
+
Authors:: Nicolas Sanguinetti (foca[http://github.com/foca]) and Simon Rozet (sr[http://github.com/sr])
|
30
|
+
License:: MIT (Check LICENSE for details)
|
data/Rakefile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "rake/testtask"
|
2
|
+
|
3
|
+
rdoc_sources = %w(hanna/rdoctask rdoc/task rake/rdoctask)
|
4
|
+
begin
|
5
|
+
require rdoc_sources.shift
|
6
|
+
rescue LoadError
|
7
|
+
retry
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'jeweler'
|
12
|
+
Jeweler::Tasks.new do |s|
|
13
|
+
s.name = "bob"
|
14
|
+
s.rubyforge_project = "integrity"
|
15
|
+
s.summary = "Bob builds!"
|
16
|
+
s.email = "info@integrityapp.com"
|
17
|
+
s.homepage = "http://integrityapp.com"
|
18
|
+
s.description = "Bob the Builder will build your code. Simple."
|
19
|
+
s.authors = ["Nicolás Sanguinetti", "Simon Rozet"]
|
20
|
+
s.files = FileList["[A-Z]*", "{lib}/**/*"]
|
21
|
+
|
22
|
+
s.add_dependency "addressable"
|
23
|
+
s.add_dependency "ninja"
|
24
|
+
s.has_rdoc = true
|
25
|
+
if s.respond_to?(:add_development_dependency)
|
26
|
+
s.add_development_dependency "sr-mg"
|
27
|
+
s.add_development_dependency "contest"
|
28
|
+
s.add_development_dependency "redgreen"
|
29
|
+
s.add_development_dependency "ruby-debug"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
rescue LoadError
|
33
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require "metric_fu" if RUBY_VERSION < "1.9"
|
38
|
+
rescue LoadError
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
require "mg"
|
43
|
+
MG.new("bob.gemspec")
|
44
|
+
rescue LoadError
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Default: run all tests"
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
SCMs = %w[git svn]
|
51
|
+
|
52
|
+
desc "Run unit tests"
|
53
|
+
task :test => SCMs.map { |scm| "test:#{scm}" } do
|
54
|
+
ruby "test/bob_test.rb"
|
55
|
+
ruby "test/test_test.rb"
|
56
|
+
end
|
57
|
+
|
58
|
+
SCMs.each { |scm|
|
59
|
+
desc "Run unit tests with #{scm}"
|
60
|
+
task "test:#{scm}" do
|
61
|
+
ruby "test/scm/#{scm}_test.rb"
|
62
|
+
end
|
63
|
+
}
|
64
|
+
|
65
|
+
(defined?(RDoc::Task) ? RDoc::Task : Rake::RDocTask).new do |rd|
|
66
|
+
rd.main = "README.rdoc"
|
67
|
+
rd.title = "Documentation for Bob the Builder"
|
68
|
+
rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
|
69
|
+
rd.rdoc_dir = "doc"
|
70
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.1
|
data/lib/bob.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
require "yaml"
|
4
|
+
require "logger"
|
5
|
+
require "time"
|
6
|
+
require "ninja"
|
7
|
+
require "addressable/uri"
|
8
|
+
|
9
|
+
require "bob/buildable"
|
10
|
+
require "bob/builder"
|
11
|
+
require "bob/scm"
|
12
|
+
|
13
|
+
module Bob
|
14
|
+
# Builds the specified <tt>buildable</tt>. This object must understand
|
15
|
+
# the API described in the README.
|
16
|
+
def self.build(buildable)
|
17
|
+
Builder.new(buildable).build
|
18
|
+
end
|
19
|
+
|
20
|
+
# Directory where the code for the different buildables will be checked out.
|
21
|
+
# Make sure the user running Bob is allowed to write to this directory.
|
22
|
+
def self.directory
|
23
|
+
Pathname(@directory || "/tmp")
|
24
|
+
end
|
25
|
+
|
26
|
+
# What to log with (must implement ruby's Logger interface). Logs to STDOUT
|
27
|
+
# by default.
|
28
|
+
def self.logger
|
29
|
+
@logger || Logger.new(STDOUT)
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
attr_writer :directory, :logger
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bob
|
2
|
+
# Mixin to add to your classes.
|
3
|
+
module Buildable
|
4
|
+
# Build itself.
|
5
|
+
def build
|
6
|
+
Bob.build(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
# What kind of repository this buildable represents. Must
|
10
|
+
# return a String ("git", "svn", etc.)
|
11
|
+
#
|
12
|
+
# <b>You must implement this in the classes where you mixin this module</b>
|
13
|
+
def scm
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Full URI to the repository to clone/checkout.
|
18
|
+
#
|
19
|
+
# <b>You must implement this in the classes where you mixin this module</b>
|
20
|
+
def uri
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
# Branch of the code you want to watch in order to build.
|
25
|
+
#
|
26
|
+
# <b>You must implement this in the classes where you mixin this module</b>
|
27
|
+
def branch
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Indentifier of the commit to build.
|
32
|
+
#
|
33
|
+
# The special identifier <tt>:head</tt> will be resolved to the head
|
34
|
+
# commit of the current branch (for example, "HEAD" under git or the
|
35
|
+
# latest revision under svn)
|
36
|
+
#
|
37
|
+
# <b>You must implement this in the classes where you mixin this module</b>
|
38
|
+
def commit
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# Script that will be run in the buildable's checked out code,
|
43
|
+
# if it returns a status code of 0 it will be considered a
|
44
|
+
# successfull build. Else it will be considered a failed build.
|
45
|
+
#
|
46
|
+
# <b>You must implement this in the classes where you mixin this module</b>
|
47
|
+
def build_script
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
|
51
|
+
# Optional callback sent when the build starts. It can be used to calculate
|
52
|
+
# the build duration for example.
|
53
|
+
def start_building
|
54
|
+
end
|
55
|
+
|
56
|
+
# Callback sent after a build finishes. The first argument is a hash with
|
57
|
+
# information about the commit.
|
58
|
+
#
|
59
|
+
# <tt>identifier</tt>:: A string with the commit identifier of the
|
60
|
+
# commit that was built
|
61
|
+
# <tt>author</tt>:: A string with the name/email of the committer
|
62
|
+
# <tt>message</tt>:: The commit message
|
63
|
+
# <tt>committed_at</tt>:: A Time object with the timestamp of the commit
|
64
|
+
#
|
65
|
+
# The second argument is a boolean indicating whether the build was
|
66
|
+
# successful. Finally, the last one is a string with the full output
|
67
|
+
# returned by the build process (STDOUT and STDERR interleaved)
|
68
|
+
#
|
69
|
+
# <b>You must implement this in the classes where you mixin this module</b>
|
70
|
+
def finish_building(commit_info, build_status, build_output)
|
71
|
+
raise NotImplementedError
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/bob/builder.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Bob
|
2
|
+
# A Builder will take care of building a buildable (wow, you didn't see
|
3
|
+
# that coming, right?).
|
4
|
+
class Builder
|
5
|
+
attr_reader :buildable
|
6
|
+
|
7
|
+
include Ninja
|
8
|
+
|
9
|
+
# Instantiate the Builder, passing an object that understands
|
10
|
+
# the <tt>Buildable</tt> interface.
|
11
|
+
def initialize(buildable)
|
12
|
+
@buildable = buildable
|
13
|
+
end
|
14
|
+
|
15
|
+
# This is where the magic happens:
|
16
|
+
#
|
17
|
+
# 1. Notify the buildable that the build is starting.
|
18
|
+
# 2. Check out the repo to the appropriate commit.
|
19
|
+
# 3. Run the build script on it.
|
20
|
+
# 4. Reports the build back to the buildable.
|
21
|
+
def build
|
22
|
+
Bob.logger.info "Building #{buildable.commit} of the #{buildable.scm} repo at #{buildable.uri}"
|
23
|
+
|
24
|
+
in_background do
|
25
|
+
buildable.start_building if buildable.respond_to?(:start_building)
|
26
|
+
|
27
|
+
scm.with_commit(buildable.commit) { |commit_info|
|
28
|
+
buildable.finish_building(commit_info, *run_build_script)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def run_build_script
|
36
|
+
build_output = nil
|
37
|
+
|
38
|
+
Bob.logger.debug("Running the build script for #{buildable.uri}")
|
39
|
+
IO.popen(build_script, "r") { |output| build_output = output.read }
|
40
|
+
Bob.logger.debug("Ran build script `#{build_script}` and got:\n#{build_output}")
|
41
|
+
|
42
|
+
[$?.success?, build_output]
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_script
|
46
|
+
"(cd #{scm.dir_for(buildable.commit)} && #{buildable.build_script} 2>&1)"
|
47
|
+
end
|
48
|
+
|
49
|
+
def scm
|
50
|
+
@scm ||= SCM.new(buildable.scm, buildable.uri, buildable.branch)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/bob/scm.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bob/scm/abstract"
|
2
|
+
|
3
|
+
module Bob
|
4
|
+
module SCM
|
5
|
+
autoload :Git, "bob/scm/git"
|
6
|
+
autoload :Svn, "bob/scm/svn"
|
7
|
+
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
# Factory to return appropriate SCM instances (according to repository kind)
|
11
|
+
def self.new(scm, uri, branch)
|
12
|
+
class_for(scm).new(uri, branch)
|
13
|
+
end
|
14
|
+
|
15
|
+
# A copy of Inflector.camelize, from ActiveSupport. It will convert
|
16
|
+
# string to UpperCamelCase.
|
17
|
+
def self.class_for(string)
|
18
|
+
class_name = string.to_s.
|
19
|
+
gsub(/\/(.?)/) { "::#{$1.upcase}" }.
|
20
|
+
gsub(/(?:^|_)(.)/) { $1.upcase }
|
21
|
+
const_get(class_name)
|
22
|
+
end
|
23
|
+
private_class_method :class_for
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Bob
|
2
|
+
module SCM
|
3
|
+
class Abstract
|
4
|
+
attr_reader :uri, :branch
|
5
|
+
|
6
|
+
def initialize(uri, branch)
|
7
|
+
@uri = Addressable::URI.parse(uri)
|
8
|
+
@branch = branch
|
9
|
+
end
|
10
|
+
|
11
|
+
# Checkout the code at the specified <tt>commit</tt> and call the
|
12
|
+
# passed block.
|
13
|
+
def with_commit(commit)
|
14
|
+
commit = resolve(commit)
|
15
|
+
checkout(commit)
|
16
|
+
yield info(commit)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Directory where the code will be checked out for the given
|
20
|
+
# <tt>commit</tt>.
|
21
|
+
def dir_for(commit)
|
22
|
+
commit = resolve(commit)
|
23
|
+
Bob.directory.join(path, "-", commit)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# Get some information about the specified <tt>commit</tt>.
|
29
|
+
# Returns a hash with:
|
30
|
+
#
|
31
|
+
# [<tt>identifier</tt>] Commit identifier
|
32
|
+
# [<tt>author</tt>] Commit author's name and email
|
33
|
+
# [<tt>message</tt>] Commit message
|
34
|
+
# [<tt>committed_at</tt>] Commit date (as a <tt>Time</tt> object)
|
35
|
+
def info(commit)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the identifier for the last commit in this branch of the
|
40
|
+
# repository.
|
41
|
+
def head
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def run(command, directory=nil)
|
46
|
+
command = "(#{directory ? "cd #{directory} && " : ""}#{command} &>/dev/null)"
|
47
|
+
Bob.logger.debug(command)
|
48
|
+
system(command) || raise(Error, "Couldn't run SCM command `#{command}`")
|
49
|
+
end
|
50
|
+
|
51
|
+
def path
|
52
|
+
@path ||= "#{uri}-#{branch}".
|
53
|
+
gsub(/[^\w_ \-]+/i, '-'). # Remove unwanted chars.
|
54
|
+
gsub(/[ \-]+/i, '-'). # No more than one of the separator in a row.
|
55
|
+
gsub(/^\-|\-$/i, '') # Remove leading/trailing separator.
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def resolve(commit)
|
61
|
+
commit == :head ? head : commit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/bob/scm/git.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bob
|
2
|
+
module SCM
|
3
|
+
class Git < Abstract
|
4
|
+
protected
|
5
|
+
|
6
|
+
def info(commit)
|
7
|
+
format = "---%nidentifier: %H%nauthor: %an \
|
8
|
+
<%ae>%nmessage: >-%n %s%ncommitted_at: %ci%n"
|
9
|
+
|
10
|
+
dump = YAML.load(`cd #{dir_for(commit)} && git show -s \
|
11
|
+
--pretty=format:"#{format}" #{commit}`)
|
12
|
+
|
13
|
+
dump.update("committed_at" => Time.parse(dump["committed_at"]))
|
14
|
+
end
|
15
|
+
|
16
|
+
def head
|
17
|
+
`git ls-remote --heads #{uri} #{branch} | cut -f1`.chomp
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def checkout(commit)
|
23
|
+
run "git clone #{uri} #{dir_for(commit)}" unless cloned?(commit)
|
24
|
+
run "git fetch origin", dir_for(commit)
|
25
|
+
run "git checkout origin/#{branch}", dir_for(commit)
|
26
|
+
run "git reset --hard #{commit}", dir_for(commit)
|
27
|
+
end
|
28
|
+
|
29
|
+
def cloned?(commit)
|
30
|
+
dir_for(commit).join(".git").directory?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/bob/scm/svn.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Bob
|
2
|
+
module SCM
|
3
|
+
class Svn < Abstract
|
4
|
+
protected
|
5
|
+
|
6
|
+
def info(revision)
|
7
|
+
dump = `svn log --non-interactive --revision #{revision} #{uri}`.split("\n")
|
8
|
+
meta = dump[1].split(" | ")
|
9
|
+
|
10
|
+
{ "identifier" => revision,
|
11
|
+
"message" => dump[3],
|
12
|
+
"author" => meta[1],
|
13
|
+
"committed_at" => Time.parse(meta[2]) }
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def head
|
18
|
+
`svn info #{uri}`.split("\n").detect { |l| l =~ /^Revision: (\d+)/ }
|
19
|
+
$1.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def checkout(revision)
|
25
|
+
unless checked_out?(revision)
|
26
|
+
run "svn co -q #{uri} #{dir_for(revision)}"
|
27
|
+
end
|
28
|
+
|
29
|
+
run "svn up -q -r#{revision}", dir_for(revision)
|
30
|
+
end
|
31
|
+
|
32
|
+
def checked_out?(commit)
|
33
|
+
dir_for(commit).join(".svn").directory?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/bob/test.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Bob
|
2
|
+
module Test
|
3
|
+
class BuildableStub
|
4
|
+
include Bob::Buildable
|
5
|
+
|
6
|
+
def self.for(repo, commit)
|
7
|
+
scm = (Bob::Test::GitRepo === repo) ? "git" : "svn"
|
8
|
+
uri =
|
9
|
+
if scm == "git"
|
10
|
+
repo.path
|
11
|
+
else
|
12
|
+
"file://#{SvnRepo.server_root}/#{repo.name}"
|
13
|
+
end
|
14
|
+
# TODO: move onto repo object?
|
15
|
+
branch = (scm == "git") ? "master" : ""
|
16
|
+
build_script = "./test"
|
17
|
+
|
18
|
+
new(scm, uri, branch, commit, build_script)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :scm, :uri, :branch, :commit, :build_script,
|
22
|
+
:repo, :status, :output, :commit_info
|
23
|
+
|
24
|
+
def initialize(scm, uri, branch, commit, build_script)
|
25
|
+
@scm = scm.to_s
|
26
|
+
@uri = uri.to_s
|
27
|
+
@branch = branch
|
28
|
+
@commit = commit
|
29
|
+
@build_script = build_script
|
30
|
+
|
31
|
+
@status = nil
|
32
|
+
@output = ""
|
33
|
+
@commit_info = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def finish_building(commit_info, status, output)
|
37
|
+
@commit_info = commit_info
|
38
|
+
@status = status ? :successful : :failed
|
39
|
+
@output = output
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Bob::Test
|
2
|
+
class AbstractSCMRepo
|
3
|
+
attr_reader :path, :name
|
4
|
+
|
5
|
+
def initialize(name, base_dir=Bob.directory)
|
6
|
+
@name = name
|
7
|
+
@path = File.join(base_dir, @name.to_s)
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_commit(message, &action)
|
11
|
+
Dir.chdir(path) do
|
12
|
+
yield action
|
13
|
+
commit(message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_failing_commit
|
18
|
+
add_commit "This commit will fail" do
|
19
|
+
system "echo '#{build_script(false)}' > test"
|
20
|
+
system "chmod +x test"
|
21
|
+
add "test"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_successful_commit
|
26
|
+
add_commit "This commit will work" do
|
27
|
+
system "echo '#{build_script(true)}' > test"
|
28
|
+
system "chmod +x test"
|
29
|
+
add "test"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def commits
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
def head
|
38
|
+
commits.last["identifier"]
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def add(file)
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def commit(message)
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
def run(command)
|
51
|
+
system "#{command} &>/dev/null"
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_script(successful=true)
|
55
|
+
<<-script
|
56
|
+
#!/bin/sh
|
57
|
+
echo "Running tests..."
|
58
|
+
exit #{successful ? 0 : 1}
|
59
|
+
script
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/abstract"
|
2
|
+
|
3
|
+
module Bob::Test
|
4
|
+
class GitRepo < AbstractSCMRepo
|
5
|
+
def create
|
6
|
+
FileUtils.mkdir(path)
|
7
|
+
|
8
|
+
Dir.chdir(path) do
|
9
|
+
run "git init"
|
10
|
+
run "git config user.name 'John Doe'"
|
11
|
+
run "git config user.email 'johndoe@example.org'"
|
12
|
+
run "echo 'just a test repo' >> README"
|
13
|
+
add "README"
|
14
|
+
commit "First commit"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def commits
|
19
|
+
Dir.chdir(path) do
|
20
|
+
commits = `git log --pretty=oneline`.collect { |l| l.split(" ").first }
|
21
|
+
commits.inject([]) do |commits, sha1|
|
22
|
+
format = "---%nmessage: >-%n %s%ntimestamp: %ci%n" +
|
23
|
+
"identifier: %H%nauthor: %n name: %an%n email: %ae%n"
|
24
|
+
commits << YAML.load(`git show -s --pretty=format:"#{format}" #{sha1}`)
|
25
|
+
end.reverse
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def head
|
30
|
+
Dir.chdir(path) do
|
31
|
+
`git log --pretty=format:%H | head -1`.chomp
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def short_head
|
36
|
+
head[0..6]
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def add(file)
|
41
|
+
run "git add #{file}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def commit(message)
|
45
|
+
run %Q{git commit -m "#{message}"}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/abstract"
|
2
|
+
require "hpricot"
|
3
|
+
|
4
|
+
module Bob::Test
|
5
|
+
class SvnRepo < AbstractSCMRepo
|
6
|
+
def self.server_root
|
7
|
+
@root ||= Bob.directory.join("svn")
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :remote
|
11
|
+
|
12
|
+
def initialize(name, base_dir=Bob.directory)
|
13
|
+
super
|
14
|
+
|
15
|
+
@path = base_dir.join("svn-#{name}")
|
16
|
+
@remote = SvnRepo.server_root.join(name.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
create_remote
|
21
|
+
|
22
|
+
run "svn checkout file://#{remote} #{path}"
|
23
|
+
|
24
|
+
add_commit("First commit") do
|
25
|
+
run "echo 'just a test repo' >> README"
|
26
|
+
add "README"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def commits
|
31
|
+
Dir.chdir(path) do
|
32
|
+
doc = Hpricot::XML(`svn log --xml`)
|
33
|
+
|
34
|
+
(doc/:log/:logentry).inject([]) { |commits, commit|
|
35
|
+
commits << { "identifier" => commit["revision"],
|
36
|
+
"message" => commit.at("msg").inner_html,
|
37
|
+
"committed_at" => Time.parse(commit.at("date").inner_html) }
|
38
|
+
}.reverse
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method :short_head, :head
|
43
|
+
|
44
|
+
protected
|
45
|
+
def add(file)
|
46
|
+
run "svn add #{file}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def commit(message)
|
50
|
+
run %Q{svn commit -m "#{message}"}
|
51
|
+
run "svn up"
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def create_remote
|
56
|
+
SvnRepo.server_root.mkdir
|
57
|
+
|
58
|
+
run "svnadmin create #{remote}"
|
59
|
+
|
60
|
+
File.open(File.join(remote, "conf", "svnserve.conf"), "w") { |f|
|
61
|
+
f.puts "[general]"
|
62
|
+
f.puts "anon-access = write"
|
63
|
+
f.puts "auth-access = write"
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/test/bob_test.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class BobTest < Test::Unit::TestCase
|
4
|
+
test "directory" do
|
5
|
+
Bob.directory = "/foo/bar"
|
6
|
+
assert_equal "/foo/bar", Bob.directory.to_s
|
7
|
+
assert_instance_of Pathname, Bob.directory
|
8
|
+
end
|
9
|
+
|
10
|
+
test "logger" do
|
11
|
+
logger = Logger.new("/tmp/bob.log")
|
12
|
+
Bob.logger = logger
|
13
|
+
|
14
|
+
assert_same logger, Bob.logger
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../helper"
|
2
|
+
|
3
|
+
class ThreadedBobTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
|
7
|
+
@repo = GitRepo.new(:test_repo)
|
8
|
+
@repo.create
|
9
|
+
end
|
10
|
+
|
11
|
+
test "with a successful threaded build" do
|
12
|
+
old_engine = Bob.engine
|
13
|
+
|
14
|
+
repo.add_successful_commit
|
15
|
+
commit_id = repo.commits.last["identifier"]
|
16
|
+
buildable = BuildableStub.for(@repo, commit_id)
|
17
|
+
|
18
|
+
begin
|
19
|
+
Thread.abort_on_exception = true
|
20
|
+
Bob.engine = Bob::Engine::Threaded.new(5)
|
21
|
+
buildable.build
|
22
|
+
Bob.engine.wait!
|
23
|
+
|
24
|
+
assert_equal :successful, buildable.status
|
25
|
+
assert_equal "Running tests...\n", buildable.output
|
26
|
+
|
27
|
+
commit = buildable.commit_info
|
28
|
+
assert_equal "This commit will work", commit["message"]
|
29
|
+
assert_equal Time.now.min, commit["committed_at"].min
|
30
|
+
ensure
|
31
|
+
Bob.engine = old_engine
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class FakeLogger
|
36
|
+
attr_reader :msg
|
37
|
+
|
38
|
+
def error(msg)
|
39
|
+
@msg = msg
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
test "when something goes wrong" do
|
44
|
+
logger = FakeLogger.new
|
45
|
+
|
46
|
+
engine = Bob::Engine::Threaded.new(2, logger)
|
47
|
+
engine.call(proc { fail "foo" })
|
48
|
+
engine.wait!
|
49
|
+
|
50
|
+
assert_equal "Exception occured during build: foo", logger.msg
|
51
|
+
end
|
52
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "contest"
|
3
|
+
require "hpricot"
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "redgreen"
|
7
|
+
require "ruby-debug"
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"),
|
12
|
+
File.expand_path(File.dirname(__FILE__) + "/../test/helper"))
|
13
|
+
|
14
|
+
require "bob"
|
15
|
+
require "bob/test"
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
include Bob
|
19
|
+
include Bob::Test
|
20
|
+
|
21
|
+
attr_reader :repo
|
22
|
+
|
23
|
+
def setup
|
24
|
+
Bob.logger = Logger.new("/dev/null")
|
25
|
+
Bob.directory = Pathname(__FILE__).dirname.join("..", "tmp").expand_path
|
26
|
+
|
27
|
+
Bob.directory.rmtree if Bob.directory.directory?
|
28
|
+
Bob.directory.mkdir
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../helper"
|
2
|
+
|
3
|
+
class BobGitTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
|
7
|
+
@repo = GitRepo.new(:test_repo)
|
8
|
+
@repo.create
|
9
|
+
end
|
10
|
+
|
11
|
+
def path(uri, branch="master")
|
12
|
+
SCM::Git.new(uri, branch).__send__(:path)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "converts repo uri into a path" do
|
16
|
+
assert_equal "git-github-com-integrity-bob-master",
|
17
|
+
path("git://github.com/integrity/bob")
|
18
|
+
assert_equal "git-example-org-foo-repo-master",
|
19
|
+
path("git@example.org:~foo/repo")
|
20
|
+
assert_equal "tmp-repo-git-master", path("/tmp/repo.git")
|
21
|
+
assert_equal "tmp-repo-git-foo", path("/tmp/repo.git", "foo")
|
22
|
+
end
|
23
|
+
|
24
|
+
test "with a successful build" do
|
25
|
+
repo.add_successful_commit
|
26
|
+
|
27
|
+
commit_id = repo.commits.last["identifier"]
|
28
|
+
|
29
|
+
buildable = BuildableStub.for(@repo, commit_id)
|
30
|
+
buildable.build
|
31
|
+
|
32
|
+
assert_equal :successful, buildable.status
|
33
|
+
assert_equal "Running tests...\n", buildable.output
|
34
|
+
assert_equal "This commit will work", buildable.commit_info["message"]
|
35
|
+
assert_equal commit_id, buildable.commit_info["identifier"]
|
36
|
+
assert buildable.commit_info["committed_at"].is_a?(Time)
|
37
|
+
end
|
38
|
+
|
39
|
+
test "with a failed build" do
|
40
|
+
repo.add_failing_commit
|
41
|
+
|
42
|
+
commit_id = repo.commits.last["identifier"]
|
43
|
+
buildable = BuildableStub.for(@repo, commit_id)
|
44
|
+
|
45
|
+
buildable.build
|
46
|
+
|
47
|
+
assert_equal :failed, buildable.status
|
48
|
+
assert_equal "Running tests...\n", buildable.output
|
49
|
+
assert_equal "This commit will fail", buildable.commit_info["message"]
|
50
|
+
assert_equal commit_id, buildable.commit_info["identifier"]
|
51
|
+
assert buildable.commit_info["committed_at"].is_a?(Time)
|
52
|
+
end
|
53
|
+
|
54
|
+
test "can build the head of a repository" do
|
55
|
+
repo.add_failing_commit
|
56
|
+
repo.add_successful_commit
|
57
|
+
|
58
|
+
buildable = BuildableStub.for(@repo, :head)
|
59
|
+
|
60
|
+
buildable.build
|
61
|
+
|
62
|
+
assert_equal :successful, buildable.status
|
63
|
+
assert_equal "Running tests...\n", buildable.output
|
64
|
+
assert_equal repo.head, buildable.commit_info["identifier"]
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../helper"
|
2
|
+
|
3
|
+
class BobSvnTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
|
7
|
+
@repo = SvnRepo.new(:test_repo)
|
8
|
+
@repo.create
|
9
|
+
end
|
10
|
+
|
11
|
+
def path(uri)
|
12
|
+
SCM::Svn.new(uri, "").__send__(:path)
|
13
|
+
end
|
14
|
+
|
15
|
+
test "converts svn repo uri into a path" do
|
16
|
+
assert_equal "http-rubygems-rubyforge-org-svn",
|
17
|
+
path("http://rubygems.rubyforge.org/svn/")
|
18
|
+
|
19
|
+
assert_equal "svn-rubyforge-org-var-svn-rubygems",
|
20
|
+
path("svn://rubyforge.org/var/svn/rubygems")
|
21
|
+
|
22
|
+
assert_equal "svn-ssh-developername-rubyforge-org-var-svn-rubygems",
|
23
|
+
path("svn+ssh://developername@rubyforge.org/var/svn/rubygems")
|
24
|
+
|
25
|
+
assert_equal "home-user-code-repo",
|
26
|
+
path("/home/user/code/repo")
|
27
|
+
end
|
28
|
+
|
29
|
+
test "with a successful build" do
|
30
|
+
repo.add_successful_commit
|
31
|
+
|
32
|
+
buildable = BuildableStub.for(@repo, "2")
|
33
|
+
buildable.build
|
34
|
+
|
35
|
+
assert_equal :successful, buildable.status
|
36
|
+
assert_equal "Running tests...\n", buildable.output
|
37
|
+
assert_equal "This commit will work", buildable.commit_info["message"]
|
38
|
+
assert_equal "2", buildable.commit_info["identifier"]
|
39
|
+
assert buildable.commit_info["committed_at"].is_a?(Time)
|
40
|
+
end
|
41
|
+
|
42
|
+
test "with a failed build" do
|
43
|
+
repo.add_failing_commit
|
44
|
+
|
45
|
+
buildable = BuildableStub.for(@repo, "2")
|
46
|
+
buildable.build
|
47
|
+
|
48
|
+
assert_equal :failed, buildable.status
|
49
|
+
assert_equal "Running tests...\n", buildable.output
|
50
|
+
assert_equal "This commit will fail", buildable.commit_info["message"]
|
51
|
+
assert_equal "2", buildable.commit_info["identifier"]
|
52
|
+
assert buildable.commit_info["committed_at"].is_a?(Time)
|
53
|
+
end
|
54
|
+
|
55
|
+
test "can build the head of a repository" do
|
56
|
+
repo.add_failing_commit
|
57
|
+
repo.add_successful_commit
|
58
|
+
|
59
|
+
buildable = BuildableStub.for(@repo, :head)
|
60
|
+
buildable.build
|
61
|
+
|
62
|
+
assert_equal :successful, buildable.status
|
63
|
+
assert_equal "Running tests...\n", buildable.output
|
64
|
+
assert_equal repo.head, buildable.commit_info["identifier"]
|
65
|
+
end
|
66
|
+
end
|
data/test/test_test.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/helper"
|
2
|
+
|
3
|
+
class BobTestTest < Test::Unit::TestCase
|
4
|
+
def assert_scm_repo(repo)
|
5
|
+
repo.create
|
6
|
+
|
7
|
+
assert_equal 1, repo.commits.size
|
8
|
+
assert_equal "First commit", repo.commits.first["message"]
|
9
|
+
|
10
|
+
repo.add_failing_commit
|
11
|
+
assert_equal 2, repo.commits.size
|
12
|
+
assert_equal "This commit will fail", repo.commits.last["message"]
|
13
|
+
assert_equal repo.commits.last["identifier"], repo.head
|
14
|
+
assert repo.short_head
|
15
|
+
|
16
|
+
repo.add_successful_commit
|
17
|
+
assert_equal 3, repo.commits.size
|
18
|
+
assert_equal "This commit will work", repo.commits.last["message"]
|
19
|
+
assert_equal repo.commits.last["identifier"], repo.head
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_buildable_stub
|
23
|
+
b = BuildableStub.new(:git, "git://github.com/ry/node", "master", "HEAD", "make")
|
24
|
+
|
25
|
+
assert_equal "git", b.scm
|
26
|
+
assert_equal "git://github.com/ry/node", b.uri
|
27
|
+
assert_equal "master", b.branch
|
28
|
+
assert_equal "HEAD", b.commit
|
29
|
+
assert_equal "make", b.build_script
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_scm_repo
|
33
|
+
assert_scm_repo(GitRepo.new(:my_test_project))
|
34
|
+
assert_scm_repo(SvnRepo.new(:my_test_project))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_buildable_git_repo
|
38
|
+
repo = GitRepo.new(:test_repo)
|
39
|
+
repo.create
|
40
|
+
|
41
|
+
b = BuildableStub.for(repo, repo.head)
|
42
|
+
|
43
|
+
assert_equal "git", b.scm
|
44
|
+
assert_equal "#{Bob.directory}/test_repo", b.uri
|
45
|
+
assert_equal "master", b.branch
|
46
|
+
assert_equal repo.head, b.commit
|
47
|
+
assert_equal "./test", b.build_script
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_buildable_svn_repo
|
51
|
+
repo = SvnRepo.new(:test_repo)
|
52
|
+
repo.create
|
53
|
+
|
54
|
+
b = BuildableStub.for(repo, repo.head)
|
55
|
+
|
56
|
+
assert_equal "svn", b.scm
|
57
|
+
assert_equal "", b.branch
|
58
|
+
assert_equal repo.head, b.commit
|
59
|
+
assert_equal "./test", b.build_script
|
60
|
+
assert_equal "file://#{Bob.directory}/svn/test_repo", b.uri
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jeroenvandijk-bob
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Nicol\xC3\xA1s Sanguinetti"
|
8
|
+
- Simon Rozet
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-09-16 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: addressable
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: ninja
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sr-mg
|
38
|
+
type: :development
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: contest
|
48
|
+
type: :development
|
49
|
+
version_requirement:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: redgreen
|
58
|
+
type: :development
|
59
|
+
version_requirement:
|
60
|
+
version_requirements: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: ruby-debug
|
68
|
+
type: :development
|
69
|
+
version_requirement:
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
description: Bob the Builder will build your code. Simple.
|
77
|
+
email: info@integrityapp.com
|
78
|
+
executables: []
|
79
|
+
|
80
|
+
extensions: []
|
81
|
+
|
82
|
+
extra_rdoc_files:
|
83
|
+
- LICENSE
|
84
|
+
- README.rdoc
|
85
|
+
files:
|
86
|
+
- LICENSE
|
87
|
+
- README.rdoc
|
88
|
+
- Rakefile
|
89
|
+
- VERSION
|
90
|
+
- lib/bob.rb
|
91
|
+
- lib/bob/buildable.rb
|
92
|
+
- lib/bob/builder.rb
|
93
|
+
- lib/bob/scm.rb
|
94
|
+
- lib/bob/scm/abstract.rb
|
95
|
+
- lib/bob/scm/git.rb
|
96
|
+
- lib/bob/scm/svn.rb
|
97
|
+
- lib/bob/test.rb
|
98
|
+
- lib/bob/test/buildable_stub.rb
|
99
|
+
- lib/bob/test/scm/abstract.rb
|
100
|
+
- lib/bob/test/scm/git.rb
|
101
|
+
- lib/bob/test/scm/svn.rb
|
102
|
+
has_rdoc: false
|
103
|
+
homepage: http://integrityapp.com
|
104
|
+
licenses:
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options:
|
107
|
+
- --charset=UTF-8
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: "0"
|
115
|
+
version:
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: "0"
|
121
|
+
version:
|
122
|
+
requirements: []
|
123
|
+
|
124
|
+
rubyforge_project: integrity
|
125
|
+
rubygems_version: 1.3.5
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: Bob builds!
|
129
|
+
test_files:
|
130
|
+
- test/bob_test.rb
|
131
|
+
- test/engine/threaded_test.rb
|
132
|
+
- test/helper.rb
|
133
|
+
- test/scm/git_test.rb
|
134
|
+
- test/scm/svn_test.rb
|
135
|
+
- test/test_test.rb
|