spriggan 0.1.11
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/sprig-purge +26 -0
- data/bin/spriggan +11 -0
- data/lib/spriggan.rb +4 -0
- data/lib/spriggan/buildjob.rb +231 -0
- data/lib/spriggan/system_calls_patch.rb +16 -0
- data/lib/spriggan/version.rb +3 -0
- data/spriggan.gemspec +24 -0
- metadata +70 -0
data/bin/sprig-purge
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
apt_root = Pathname.new(ENV['HOME']) + 'apt'
|
5
|
+
|
6
|
+
unless apt_root.directory?
|
7
|
+
warn "Not running as spriggan user; aborting."
|
8
|
+
Kernel.exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
metapkg_name = ARGV[0]
|
12
|
+
|
13
|
+
pkg_names = Dir.chdir(apt_root) do
|
14
|
+
a = `find . -name "#{metapkg_name}_*.deb"`.split("\n")
|
15
|
+
b = `find . -name "#{metapkg_name}-*.deb"`.split("\n")
|
16
|
+
(a + b).map{ |pkg| Pathname.new(pkg).basename.to_s.split('_').first }
|
17
|
+
end
|
18
|
+
|
19
|
+
pkg_names.each do |pkg_name|
|
20
|
+
system("reprepro -b #{apt_root} remove stable #{pkg_name}")
|
21
|
+
end
|
22
|
+
|
23
|
+
sprig_dir = Pathname.new(ENV['HOME']) + 'project' + "#{metapkg_name}.git" + 'spriggan'
|
24
|
+
sprig_dir.rmtree if sprig_dir.directory?
|
25
|
+
|
26
|
+
puts "OK"
|
data/bin/spriggan
ADDED
data/lib/spriggan.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'pathname'
|
3
|
+
require 'yaml'
|
4
|
+
require 'digest'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
require 'spriggan'
|
8
|
+
require 'spriggan/system_calls_patch'
|
9
|
+
|
10
|
+
class Spriggan::BuildJob
|
11
|
+
def initialize(source_dir, dest_dir)
|
12
|
+
@bare_repo = Pathname.new(source_dir).expand_path
|
13
|
+
@apt_root = Pathname.new(dest_dir).expand_path
|
14
|
+
|
15
|
+
unless @bare_repo.directory? and (@bare_repo + 'config').file?
|
16
|
+
raise ArgumentError, 'invalid source'
|
17
|
+
end
|
18
|
+
|
19
|
+
unless @apt_root.directory? and (@apt_root + 'conf' + 'distributions').file?
|
20
|
+
raise ArgumentError, 'invalid destination'
|
21
|
+
end
|
22
|
+
|
23
|
+
@project = {}
|
24
|
+
@project[:root] = @bare_repo + 'spriggan'
|
25
|
+
@project[:name] = @bare_repo.basename.to_s.sub(/\.git$/, '')
|
26
|
+
@project[:name] = @bare_repo.parent.basename.to_s if @project[:name] == ''
|
27
|
+
@project[:cache] = @project[:root] + 'cache'
|
28
|
+
|
29
|
+
@build = {}
|
30
|
+
@build[:timestamp] = Time.now.strftime("%Y%m%d%H%M%S")
|
31
|
+
@build[:id] = @build[:timestamp] + '-' + rand(2**40).to_s(32).rjust(8, '0')
|
32
|
+
@build[:root] = @project[:root] + 'jobs' + @build[:id]
|
33
|
+
|
34
|
+
@build[:files] = @build[:root] + 'fs'
|
35
|
+
@build[:pkgs] = @build[:root] + 'pkgs'
|
36
|
+
@build[:debs] = @build[:root] + 'debs'
|
37
|
+
|
38
|
+
@packages = {}
|
39
|
+
@signatures = {'packages' => {}, 'metapackage' => nil}
|
40
|
+
end
|
41
|
+
|
42
|
+
def run!
|
43
|
+
[@project[:cache], @build[:files], @build[:pkgs], @build[:debs]].each(&:mkpath)
|
44
|
+
|
45
|
+
begin
|
46
|
+
execute_mutations!
|
47
|
+
ensure
|
48
|
+
@build[:root].rmtree if @build[:root].directory?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def execute_mutations!
|
56
|
+
checkout_repo!
|
57
|
+
|
58
|
+
do_project_specific_build!
|
59
|
+
|
60
|
+
generate_packages!
|
61
|
+
generate_metapackage!
|
62
|
+
|
63
|
+
publish_packages!
|
64
|
+
end
|
65
|
+
|
66
|
+
def checkout_repo!
|
67
|
+
@project[:sha] = `git --git-dir=#{@bare_repo} show-ref -s master`.strip
|
68
|
+
@project[:tag] = `git --git-dir=#{@bare_repo} describe --tags #{@project[:sha]}`.strip
|
69
|
+
|
70
|
+
@build[:files].parent.mkpath
|
71
|
+
system!("git clone -s #{@bare_repo} #{@build[:files]}")
|
72
|
+
|
73
|
+
Dir.chdir(@build[:files]) do
|
74
|
+
system!("git checkout -q -f #{@project[:sha]}")
|
75
|
+
end
|
76
|
+
|
77
|
+
@project[:conf] = @build[:files] + 'spriggan'
|
78
|
+
@project.update(YAML.load((@project[:conf] + 'project.yml').open))
|
79
|
+
|
80
|
+
@project[:signatures] = @project[:root] + 'signatures.yml'
|
81
|
+
if @project[:signatures].file?
|
82
|
+
@signatures = YAML.load(@project[:signatures].open)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def do_project_specific_build!
|
87
|
+
Dir.chdir(@build[:files]) do
|
88
|
+
if bootstrap = @project['bootstrap']
|
89
|
+
system!(bootstrap['command'])
|
90
|
+
end
|
91
|
+
|
92
|
+
if resolve_deps = @project['resolve dependencies']
|
93
|
+
if mountpoint = resolve_deps['keep in shared cache']
|
94
|
+
with_cache_mounted(@project[:cache], @build[:files] + mountpoint) do
|
95
|
+
system!(resolve_deps['command'])
|
96
|
+
end
|
97
|
+
else
|
98
|
+
system!(resolve_deps['command'])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if build = @project['build']
|
103
|
+
system!(build['command'])
|
104
|
+
end
|
105
|
+
|
106
|
+
@project['packages'].each do |pkg_name, pkg|
|
107
|
+
pkg_dir = @build[:pkgs] + pkg_name
|
108
|
+
pkg_dir.mkpath
|
109
|
+
|
110
|
+
system!(pkg['install command'] + " " + Shellwords.escape(pkg_dir.to_s))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_packages!
|
116
|
+
@packages = {}
|
117
|
+
|
118
|
+
@build[:pkgs].children.each do |pkg_dir|
|
119
|
+
next unless pkg_dir.directory? and (pkg_dir.children.length > 0)
|
120
|
+
@packages[pkg_dir.basename.to_s] = generate_package!(pkg_dir)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def generate_package!(pkg_dir)
|
125
|
+
pkg = {}
|
126
|
+
|
127
|
+
pkg[:shortname] = pkg_dir.basename.to_s
|
128
|
+
pkg[:name] = "#{@project[:name]}-#{pkg[:shortname]}"
|
129
|
+
pkg[:signature] = hash_package(pkg_dir)
|
130
|
+
pkg[:vsn] = debian_version_str(@project[:tag], @build[:timestamp], pkg[:signature])
|
131
|
+
|
132
|
+
pkg.update(@project['packages'][pkg_dir.basename.to_s])
|
133
|
+
|
134
|
+
deps_str = ""
|
135
|
+
if pkg_deps = pkg['dependencies']
|
136
|
+
deps_str = pkg_deps.map{ |dep| "-d #{Shellwords.escape(dep.to_s)}" }.join(" ")
|
137
|
+
end
|
138
|
+
|
139
|
+
if pkg[:signature] != @signatures['packages'][pkg[:shortname]]
|
140
|
+
Dir.chdir(@build[:debs]) do
|
141
|
+
system("fpm -s dir -t deb --deb-user root --deb-group root -C #{Shellwords.escape(pkg_dir.to_s)} -n #{Shellwords.escape(pkg[:name])} -v #{Shellwords.escape(pkg[:vsn])} -a all #{deps_str} .")
|
142
|
+
end
|
143
|
+
|
144
|
+
@signatures['packages'][pkg[:shortname]] = pkg[:signature]
|
145
|
+
end
|
146
|
+
|
147
|
+
pkg
|
148
|
+
end
|
149
|
+
|
150
|
+
def hash_package(pkg_dir)
|
151
|
+
hashes = []
|
152
|
+
pkg_dir.find do |f|
|
153
|
+
next unless f.file?
|
154
|
+
hashes << [f.relative_path_from(pkg_dir).to_s, Digest::SHA1.file(f).hexdigest]
|
155
|
+
end
|
156
|
+
manifest = hashes.sort.map{ |(name, hsh)| "#{name} #{hsh}" }.join("\n")
|
157
|
+
Digest::SHA1.hexdigest(manifest)
|
158
|
+
end
|
159
|
+
|
160
|
+
def generate_metapackage!
|
161
|
+
@build[:signature] = hash_metapackage
|
162
|
+
@build[:vsn] = debian_version_str(@project[:tag], @build[:timestamp], @build[:signature])
|
163
|
+
|
164
|
+
deps_str = @packages.map{ |pkg_name, pkg| "-d #{pkg[:name]}" }.sort.join(' ')
|
165
|
+
hooks_str = %w(before-install after-install before-remove after-remove).map{ |h| @project[:conf] + h }.find_all{ |h| h.file? }.map{ |h| "--#{h.basename.to_s} #{h}" }.join(" ")
|
166
|
+
|
167
|
+
if @build[:signature] != @signatures['metapackage']
|
168
|
+
Dir.mktmpdir do |tmpdir|
|
169
|
+
Dir.chdir(@build[:debs]) do
|
170
|
+
system("fpm -s dir -t deb --deb-user root --deb-group root -C #{Shellwords.escape(tmpdir.to_s)} -n #{Shellwords.escape(@project[:name])} -v #{@build[:vsn]} -a all #{hooks_str} #{deps_str} .")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
@signatures['metapackage'] = @build[:signature]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def hash_metapackage
|
179
|
+
sigs = {}
|
180
|
+
@packages.each{ |pkg_name, pkg| sigs[pkg_name] = @signatures['packages'][pkg_name] }
|
181
|
+
manifest = sigs.to_a.sort.map{ |(pkg_name,sha)| "#{pkg_name} #{sha}" }.join("\n")
|
182
|
+
Digest::SHA1.hexdigest(manifest)
|
183
|
+
end
|
184
|
+
|
185
|
+
def publish_packages!
|
186
|
+
@build[:debs].children.each do |deb|
|
187
|
+
system("dpkg-sig -k spriggan@peripia.com --sign builder #{Shellwords.escape(deb.to_s)}")
|
188
|
+
system("reprepro -b #{Shellwords.escape(@apt_root.to_s)} includedeb stable #{Shellwords.escape(deb.to_s)}")
|
189
|
+
end
|
190
|
+
|
191
|
+
@project[:signatures].open('w'){ |f| f.write( @signatures.to_yaml ) }
|
192
|
+
end
|
193
|
+
|
194
|
+
def with_cache_mounted(cache_dir, mountpoint)
|
195
|
+
cache_dir.mkpath
|
196
|
+
mountpoint.parent.mkpath
|
197
|
+
|
198
|
+
mountpoint.make_symlink(cache_dir)
|
199
|
+
|
200
|
+
begin
|
201
|
+
yield
|
202
|
+
ensure
|
203
|
+
mountpoint.unlink
|
204
|
+
FileUtils.cp_r(cache_dir, mountpoint)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def debian_version_str(project_tag, build_timestamp, pkg_sha)
|
209
|
+
rev_parts = project_tag.split('-')
|
210
|
+
rev_base_version = rev_parts.shift.sub(/^v/, '')
|
211
|
+
|
212
|
+
rev_advance = nil
|
213
|
+
if rev_parts.last =~ /^g[0-9a-f]{6,}$/
|
214
|
+
rev_cur_ref = rev_parts.pop
|
215
|
+
rev_commits_since = rev_parts.pop
|
216
|
+
rev_advance = [rev_commits_since, rev_cur_ref].join('+')
|
217
|
+
end
|
218
|
+
|
219
|
+
if rev_parts.length > 0
|
220
|
+
rev_full_version = [rev_base_version, rev_parts.join('+')].join('~')
|
221
|
+
else
|
222
|
+
rev_full_version = rev_base_version
|
223
|
+
end
|
224
|
+
|
225
|
+
upstream_ver = [rev_full_version, rev_advance].compact.join('+')
|
226
|
+
deb_ver = [build_timestamp, pkg_sha[0...7]].join('+')
|
227
|
+
|
228
|
+
[upstream_ver, deb_ver].join('-')
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Kernel
|
2
|
+
PURGED_ENV = {
|
3
|
+
'GIT_DIR' => nil,
|
4
|
+
'GEM_HOME' => nil,
|
5
|
+
'GEM_PATH' => nil,
|
6
|
+
'BUNDLE_BIN_PATH' => nil,
|
7
|
+
'BUNDLE_GEMFILE' => nil,
|
8
|
+
'RUBYOPT' => nil
|
9
|
+
}
|
10
|
+
|
11
|
+
def system!(cmd_line)
|
12
|
+
succeeded = system(PURGED_ENV, cmd_line)
|
13
|
+
raise SystemCallError.new(nil, $?.exitstatus) unless succeeded
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
data/spriggan.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path("../lib/spriggan/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'spriggan'
|
5
|
+
s.rubyforge_project = 'spriggan'
|
6
|
+
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.version = Spriggan::VERSION
|
9
|
+
|
10
|
+
s.summary = "Git-to-deb compiler"
|
11
|
+
s.description = "Git post-receive hook debian-package build-system"
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.authors = ["Sean Keith McAuley"]
|
15
|
+
s.email = 'tsu@peripia.com'
|
16
|
+
s.homepage = 'https://github.com/tsutsu/spriggan'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_dependency 'fpm'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spriggan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.11
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sean Keith McAuley
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fpm
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Git post-receive hook debian-package build-system
|
31
|
+
email: tsu@peripia.com
|
32
|
+
executables:
|
33
|
+
- sprig-purge
|
34
|
+
- spriggan
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- bin/sprig-purge
|
39
|
+
- bin/spriggan
|
40
|
+
- lib/spriggan.rb
|
41
|
+
- lib/spriggan/buildjob.rb
|
42
|
+
- lib/spriggan/system_calls_patch.rb
|
43
|
+
- lib/spriggan/version.rb
|
44
|
+
- spriggan.gemspec
|
45
|
+
homepage: https://github.com/tsutsu/spriggan
|
46
|
+
licenses:
|
47
|
+
- MIT
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project: spriggan
|
66
|
+
rubygems_version: 1.8.23
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Git-to-deb compiler
|
70
|
+
test_files: []
|