spriggan 0.1.11
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/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: []
|