akabei 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +14 -0
- data/akabei.gemspec +29 -0
- data/bin/akabei +9 -0
- data/lib/akabei.rb +5 -0
- data/lib/akabei/abs.rb +60 -0
- data/lib/akabei/archive_utils.rb +75 -0
- data/lib/akabei/attr_path.rb +20 -0
- data/lib/akabei/builder.rb +108 -0
- data/lib/akabei/chroot_tree.rb +81 -0
- data/lib/akabei/cli.rb +172 -0
- data/lib/akabei/error.rb +4 -0
- data/lib/akabei/package.rb +174 -0
- data/lib/akabei/package_entry.rb +110 -0
- data/lib/akabei/package_info.rb +68 -0
- data/lib/akabei/repository.rb +156 -0
- data/lib/akabei/signer.rb +75 -0
- data/lib/akabei/thor_handler.rb +14 -0
- data/lib/akabei/version.rb +3 -0
- data/spec/akabei/abs_spec.rb +95 -0
- data/spec/akabei/archive_utils_spec.rb +44 -0
- data/spec/akabei/builder_spec.rb +129 -0
- data/spec/akabei/chroot_tree_spec.rb +108 -0
- data/spec/akabei/cli_spec.rb +5 -0
- data/spec/akabei/package_entry_spec.rb +70 -0
- data/spec/akabei/package_info_spec.rb +17 -0
- data/spec/akabei/package_spec.rb +54 -0
- data/spec/akabei/repository_spec.rb +157 -0
- data/spec/akabei/signer_spec.rb +5 -0
- data/spec/data/input/abs.tar.gz +0 -0
- data/spec/data/input/htop-vi.tar.gz +0 -0
- data/spec/data/input/makepkg.x86_64.conf +140 -0
- data/spec/data/input/nkf-2.1.3-1-x86_64.pkg.tar.xz +0 -0
- data/spec/data/input/nkf.PKGINFO +22 -0
- data/spec/data/input/nkf.tar.gz +0 -0
- data/spec/data/input/pacman.x86_64.conf +99 -0
- data/spec/data/input/ruby.PKGINFO +38 -0
- data/spec/data/input/test.db +0 -0
- data/spec/data/input/test.files +0 -0
- data/spec/integration/build_spec.rb +105 -0
- data/spec/spec_helper.rb +110 -0
- metadata +225 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'akabei/attr_path'
|
2
|
+
require 'akabei/error'
|
3
|
+
require 'pathname'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
module Akabei
|
7
|
+
class ChrootTree
|
8
|
+
class CommandFailed < Error
|
9
|
+
attr_reader :args
|
10
|
+
def initialize(args)
|
11
|
+
super("command failed: #{args.join(' ')}")
|
12
|
+
@args = args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
extend AttrPath
|
17
|
+
attr_path_accessor :makepkg_config, :pacman_config
|
18
|
+
|
19
|
+
def initialize(root, arch)
|
20
|
+
@root = root && Pathname.new(root)
|
21
|
+
@arch = arch
|
22
|
+
end
|
23
|
+
|
24
|
+
BASE_PACKAGES = %w[base base-devel sudo]
|
25
|
+
|
26
|
+
def with_chroot(&block)
|
27
|
+
if @root
|
28
|
+
mkarchroot(*BASE_PACKAGES)
|
29
|
+
block.call
|
30
|
+
else
|
31
|
+
@root = Pathname.new(Dir.mktmpdir)
|
32
|
+
begin
|
33
|
+
mkarchroot(*BASE_PACKAGES)
|
34
|
+
block.call
|
35
|
+
ensure
|
36
|
+
execute('rm', '-rf', @root.to_s)
|
37
|
+
@root = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def makechrootpkg(dir, env)
|
43
|
+
execute('makechrootpkg', '-cur', @root.to_s, chdir: dir, env: env)
|
44
|
+
end
|
45
|
+
|
46
|
+
def mkarchroot(*args)
|
47
|
+
cmd = ['mkarchroot']
|
48
|
+
[['-M', makepkg_config], ['-C', pacman_config]].each do |flag, path|
|
49
|
+
if path
|
50
|
+
cmd << flag << path.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
cmd << @root.join('root').to_s
|
54
|
+
execute(*(cmd + args))
|
55
|
+
end
|
56
|
+
|
57
|
+
def arch_nspawn(*args)
|
58
|
+
execute('arch-nspawn', @root.join('root').to_s, *args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute(*args)
|
62
|
+
if args.last.is_a?(Hash)
|
63
|
+
opts = args.last
|
64
|
+
if opts.has_key?(:env)
|
65
|
+
opts = opts.dup
|
66
|
+
env = opts.delete(:env)
|
67
|
+
env.each do |k, v|
|
68
|
+
args.unshift("#{k}=#{v}")
|
69
|
+
end
|
70
|
+
args.unshift('env')
|
71
|
+
args[-1] = opts
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
puts "Execute: #{args.join(' ')}"
|
76
|
+
unless system('sudo', 'setarch', @arch, *args)
|
77
|
+
raise CommandFailed.new(args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/akabei/cli.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'akabei/abs'
|
2
|
+
require 'akabei/builder'
|
3
|
+
require 'akabei/chroot_tree'
|
4
|
+
require 'akabei/package'
|
5
|
+
require 'akabei/repository'
|
6
|
+
require 'akabei/signer'
|
7
|
+
require 'pathname'
|
8
|
+
require 'thor'
|
9
|
+
require 'tmpdir'
|
10
|
+
|
11
|
+
module Akabei
|
12
|
+
class CLI < Thor
|
13
|
+
module CommonOptions
|
14
|
+
COMMON_OPTIONS = {
|
15
|
+
repo_key: {
|
16
|
+
desc: 'GPG key to sign repository database',
|
17
|
+
banner: 'GPGKEY',
|
18
|
+
type: :string
|
19
|
+
},
|
20
|
+
repo_name: {
|
21
|
+
desc: 'Name of the repository',
|
22
|
+
banner: 'NAME',
|
23
|
+
type: :string,
|
24
|
+
required: true,
|
25
|
+
},
|
26
|
+
srcdest: {
|
27
|
+
desc: 'Path to the directory to store sources',
|
28
|
+
banner: 'FILE',
|
29
|
+
type: :string,
|
30
|
+
},
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def common_options(*opts)
|
34
|
+
opts.each do |opt|
|
35
|
+
unless COMMON_OPTIONS.has_key?(opt)
|
36
|
+
raise "No such common option: #{opt}"
|
37
|
+
end
|
38
|
+
option(opt, COMMON_OPTIONS[opt])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
extend CommonOptions
|
43
|
+
|
44
|
+
desc 'build DIR', 'Build package in chroot environment'
|
45
|
+
option :chroot_dir,
|
46
|
+
desc: 'Path to chroot top',
|
47
|
+
banner: 'DIR',
|
48
|
+
type: :string
|
49
|
+
option :makepkg_config,
|
50
|
+
desc: 'Path to makepkg.conf used in chroot',
|
51
|
+
banner: 'FILE',
|
52
|
+
type: :string
|
53
|
+
option :pacman_config,
|
54
|
+
desc: 'Path to pacman.conf used in chroot',
|
55
|
+
banner: 'FILE',
|
56
|
+
type: :string
|
57
|
+
option :package_key,
|
58
|
+
desc: 'GPG key to sign packages',
|
59
|
+
banner: 'GPGKEY',
|
60
|
+
type: :string
|
61
|
+
option :logdest,
|
62
|
+
desc: 'Path to the directory to store logs',
|
63
|
+
banner: 'FILE',
|
64
|
+
type: :string
|
65
|
+
option :repo_dir,
|
66
|
+
desc: 'Path to the repository',
|
67
|
+
banner: 'DIR',
|
68
|
+
type: :string,
|
69
|
+
required: true
|
70
|
+
option :arch,
|
71
|
+
desc: 'Archtecture',
|
72
|
+
banner: 'ARCH',
|
73
|
+
enum: %w[i686 x86_64],
|
74
|
+
required: true
|
75
|
+
common_options :repo_name, :repo_key, :srcdest
|
76
|
+
def build(package_dir)
|
77
|
+
chroot = ChrootTree.new(options[:chroot_dir], options[:arch])
|
78
|
+
if options[:makepkg_config]
|
79
|
+
chroot.makepkg_config = options[:makepkg_config]
|
80
|
+
end
|
81
|
+
if options[:pacman_config]
|
82
|
+
chroot.pacman_config = options[:pacman_config]
|
83
|
+
end
|
84
|
+
|
85
|
+
repo_db = Repository.new
|
86
|
+
repo_db.signer = options[:repo_key] && Signer.new(options[:repo_key])
|
87
|
+
repo_files = Repository.new
|
88
|
+
repo_files.include_files = true
|
89
|
+
|
90
|
+
builder = Builder.new
|
91
|
+
builder.signer = options[:package_key] && Signer.new(options[:package_key])
|
92
|
+
builder.srcdest = options[:srcdest]
|
93
|
+
builder.logdest = options[:logdest]
|
94
|
+
|
95
|
+
repo_path = Pathname.new(options[:repo_dir])
|
96
|
+
repo_name = options[:repo_name]
|
97
|
+
builder.pkgdest = repo_path
|
98
|
+
|
99
|
+
db_path = repo_path.join("#{repo_name}.db")
|
100
|
+
files_path = repo_path.join("#{repo_name}.files")
|
101
|
+
repo_db.load(db_path)
|
102
|
+
repo_files.load(files_path)
|
103
|
+
|
104
|
+
abs = Abs.new(repo_path.join("#{repo_name}.abs.tar.gz"), repo_name)
|
105
|
+
|
106
|
+
chroot.with_chroot do
|
107
|
+
packages = builder.build_package(package_dir, chroot)
|
108
|
+
packages.each do |package|
|
109
|
+
repo_db.add(package)
|
110
|
+
repo_files.add(package)
|
111
|
+
end
|
112
|
+
abs.add(package_dir, builder)
|
113
|
+
repo_db.save(db_path)
|
114
|
+
repo_files.save(files_path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
desc 'abs-add DIR ABS_TARBALL', 'Add the package inside DIR to ABS_TARBALL'
|
119
|
+
common_options :repo_name, :srcdest
|
120
|
+
def abs_add(package_dir, abs_path)
|
121
|
+
builder = Builder.new
|
122
|
+
builder.srcdest = options[:srcdest]
|
123
|
+
abs = Abs.new(abs_path, options[:repo_name])
|
124
|
+
abs.add(package_dir, builder)
|
125
|
+
end
|
126
|
+
|
127
|
+
desc 'abs-remove PKG_NAME ABS_TARBALL', 'Remove PKG_NAME from ABS_TARBALL'
|
128
|
+
common_options :repo_name
|
129
|
+
def abs_remove(package_name, abs_path)
|
130
|
+
abs = Abs.new(abs_path, options[:repo_name])
|
131
|
+
abs.remove(package_name)
|
132
|
+
end
|
133
|
+
|
134
|
+
desc 'repo-add PACKAGE_PATH REPOSITORY_DB', 'Add PACKAGE_PATH to REPOSITORY_DB'
|
135
|
+
common_options :repo_key
|
136
|
+
def repo_add(package_path, db_path)
|
137
|
+
repo = Repository.new
|
138
|
+
repo.signer = options[:repo_key] && Signer.new(options[:repo_key])
|
139
|
+
repo.load(db_path)
|
140
|
+
repo.add(Package.new(package_path))
|
141
|
+
repo.save(db_path)
|
142
|
+
end
|
143
|
+
|
144
|
+
desc 'repo-remove PACKAGE_NAME REPOSITORY_DB', 'Remove PACKAGE_NAME from REPOSITORY_DB'
|
145
|
+
common_options :repo_key
|
146
|
+
def repo_remove(package_name, db_path)
|
147
|
+
repo = Repository.new
|
148
|
+
repo.signer = options[:repo_key] && Signer.new(options[:repo_key])
|
149
|
+
repo.load(db_path)
|
150
|
+
repo.remove(package_name)
|
151
|
+
repo.save(db_path)
|
152
|
+
end
|
153
|
+
|
154
|
+
desc 'files-add PACKAGE_PATH FILES_DB', 'Add PACKAGE_PATH to FILES_DB'
|
155
|
+
def files_add(package_path, db_path)
|
156
|
+
repo = Repository.new
|
157
|
+
repo.include_files = true
|
158
|
+
repo.load(db_path)
|
159
|
+
repo.add(Package.new(package_path))
|
160
|
+
repo.save(db_path)
|
161
|
+
end
|
162
|
+
|
163
|
+
desc 'files-remove PACKAGE_NAME FILES_DB', 'Remove PACKAGE_NAME from FILES_DB'
|
164
|
+
def files_remove(package_name, db_path)
|
165
|
+
repo = Repository.new
|
166
|
+
repo.include_files = true
|
167
|
+
repo.load(db_path)
|
168
|
+
repo.remove(package_name)
|
169
|
+
repo.save(db_path)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/lib/akabei/error.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'akabei/archive_utils'
|
2
|
+
require 'akabei/error'
|
3
|
+
require 'akabei/package_entry'
|
4
|
+
require 'akabei/package_info'
|
5
|
+
require 'base64'
|
6
|
+
require 'digest'
|
7
|
+
|
8
|
+
module Akabei
|
9
|
+
class Package
|
10
|
+
class NotFound < Akabei::Error
|
11
|
+
attr_reader :path, :archive
|
12
|
+
|
13
|
+
def initialize(archive, path)
|
14
|
+
super("#{path} is not found in #{archive}")
|
15
|
+
@path = path
|
16
|
+
@archive = archive
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
def initialize(path)
|
23
|
+
@path = Pathname.new(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def pkginfo
|
27
|
+
@pkginfo ||= extract_pkginfo
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_pkginfo
|
31
|
+
ArchiveUtils.each_entry(@path.to_s) do |entry, archive|
|
32
|
+
if entry.pathname == '.PKGINFO'
|
33
|
+
return PackageInfo.parse(archive.read_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
raise NotFound.new(@path, '.PKGINFO')
|
37
|
+
end
|
38
|
+
|
39
|
+
def db_name
|
40
|
+
"#{pkgname}-#{pkgver}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def csize
|
44
|
+
@path.size
|
45
|
+
end
|
46
|
+
|
47
|
+
def isize
|
48
|
+
pkginfo.size
|
49
|
+
end
|
50
|
+
|
51
|
+
def md5sum
|
52
|
+
@md5sum ||= compute_checksum('MD5')
|
53
|
+
end
|
54
|
+
|
55
|
+
def sha256sum
|
56
|
+
@sha256sum ||= compute_checksum('SHA256')
|
57
|
+
end
|
58
|
+
|
59
|
+
def compute_checksum(algo)
|
60
|
+
Digest(algo).file(@path).hexdigest
|
61
|
+
end
|
62
|
+
|
63
|
+
%w[
|
64
|
+
pkgname
|
65
|
+
pkgver
|
66
|
+
pkgbase
|
67
|
+
pkgdesc
|
68
|
+
url
|
69
|
+
license
|
70
|
+
arch
|
71
|
+
builddate
|
72
|
+
packager
|
73
|
+
replaces
|
74
|
+
|
75
|
+
provides
|
76
|
+
].each do |attr|
|
77
|
+
define_method(attr) do
|
78
|
+
pkginfo.public_send(attr)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
%w[
|
83
|
+
group
|
84
|
+
depend
|
85
|
+
conflict
|
86
|
+
optdepend
|
87
|
+
makedepend
|
88
|
+
checkdepend
|
89
|
+
].each do |attr|
|
90
|
+
define_method("#{attr}s") do
|
91
|
+
pkginfo.public_send(attr)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def filename
|
96
|
+
@path.basename.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def name
|
100
|
+
pkginfo.pkgname
|
101
|
+
end
|
102
|
+
|
103
|
+
def base
|
104
|
+
pkginfo.pkgbase
|
105
|
+
end
|
106
|
+
|
107
|
+
def version
|
108
|
+
pkginfo.pkgver
|
109
|
+
end
|
110
|
+
|
111
|
+
def desc
|
112
|
+
pkginfo.pkgdesc
|
113
|
+
end
|
114
|
+
|
115
|
+
def pgpsig
|
116
|
+
sig_file = @path.parent.join("#{filename}.sig")
|
117
|
+
if sig_file.readable?
|
118
|
+
Base64.strict_encode64(sig_file.binread)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def files
|
123
|
+
xs = []
|
124
|
+
ArchiveUtils.each_entry(@path) do |entry|
|
125
|
+
unless entry.pathname.start_with?('.')
|
126
|
+
xs << entry.pathname
|
127
|
+
end
|
128
|
+
end
|
129
|
+
xs.sort
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_entry
|
133
|
+
entry = PackageEntry.new
|
134
|
+
%w[
|
135
|
+
filename
|
136
|
+
name
|
137
|
+
base
|
138
|
+
version
|
139
|
+
desc
|
140
|
+
groups
|
141
|
+
csize
|
142
|
+
isize
|
143
|
+
|
144
|
+
md5sum
|
145
|
+
sha256sum
|
146
|
+
|
147
|
+
pgpsig
|
148
|
+
|
149
|
+
url
|
150
|
+
license
|
151
|
+
builddate
|
152
|
+
packager
|
153
|
+
replaces
|
154
|
+
|
155
|
+
provides
|
156
|
+
optdepends
|
157
|
+
makedepends
|
158
|
+
checkdepends
|
159
|
+
|
160
|
+
files
|
161
|
+
].each do |attr|
|
162
|
+
val = send(attr)
|
163
|
+
if val.is_a?(Array)
|
164
|
+
val.each do |v|
|
165
|
+
entry.add(attr, v)
|
166
|
+
end
|
167
|
+
else
|
168
|
+
entry.add(attr, val)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
entry
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'akabei/error'
|
2
|
+
|
3
|
+
module Akabei
|
4
|
+
class PackageEntry
|
5
|
+
ARRAY_DESC_ATTRIBUTES = %w[
|
6
|
+
groups
|
7
|
+
license
|
8
|
+
replaces
|
9
|
+
].freeze
|
10
|
+
DESC_ATTRIBUTES = %w[
|
11
|
+
filename
|
12
|
+
name
|
13
|
+
base
|
14
|
+
version
|
15
|
+
desc
|
16
|
+
csize
|
17
|
+
isize
|
18
|
+
md5sum
|
19
|
+
sha256sum
|
20
|
+
pgpsig
|
21
|
+
url
|
22
|
+
arch
|
23
|
+
builddate
|
24
|
+
packager
|
25
|
+
].freeze
|
26
|
+
ARRAY_DEPENDS_ATTRIBUTES = %w[
|
27
|
+
depends
|
28
|
+
conflicts
|
29
|
+
provides
|
30
|
+
optdepends
|
31
|
+
makedepends
|
32
|
+
checkdepends
|
33
|
+
].freeze
|
34
|
+
ARRAY_FILES_ATTRIBUTES = %w[files].freeze
|
35
|
+
|
36
|
+
ARRAY_ATTRIBUTES = (ARRAY_DESC_ATTRIBUTES + ARRAY_DEPENDS_ATTRIBUTES + ARRAY_FILES_ATTRIBUTES).freeze
|
37
|
+
|
38
|
+
attr_reader *ARRAY_ATTRIBUTES
|
39
|
+
attr_reader *DESC_ATTRIBUTES
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
ARRAY_ATTRIBUTES.each do |key|
|
43
|
+
instance_variable_set("@#{key}", [])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add(key, val)
|
48
|
+
ivar = "@#{key}".intern
|
49
|
+
if ARRAY_ATTRIBUTES.include?(key)
|
50
|
+
instance_variable_get(ivar) << val
|
51
|
+
elsif DESC_ATTRIBUTES.include?(key)
|
52
|
+
if v = instance_variable_get(ivar)
|
53
|
+
raise Error.new("Multiple entry found: #{v} and #{val}")
|
54
|
+
else
|
55
|
+
instance_variable_set(ivar, val)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise Error.new("Unknown entry key: #{key}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def ==(other)
|
63
|
+
(ARRAY_ATTRIBUTES + DESC_ATTRIBUTES).all? do |attr|
|
64
|
+
public_send(attr) == other.public_send(attr)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_desc(io)
|
69
|
+
ARRAY_DESC_ATTRIBUTES.each do |attr|
|
70
|
+
write_array(io, attr)
|
71
|
+
end
|
72
|
+
|
73
|
+
DESC_ATTRIBUTES.each do |attr|
|
74
|
+
write_string(io, attr)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_depends(io)
|
79
|
+
ARRAY_DEPENDS_ATTRIBUTES.each do |attr|
|
80
|
+
write_array(io, attr)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def write_files(io)
|
85
|
+
unless write_array(io, 'files')
|
86
|
+
warn 'WARNING: files attribute is empty.'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_array(io, attr)
|
91
|
+
arr = instance_variable_get("@#{attr}")
|
92
|
+
unless arr.empty?
|
93
|
+
write_entry(io, attr, arr)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def write_string(io, attr)
|
98
|
+
if v = instance_variable_get("@#{attr}")
|
99
|
+
write_entry(io, attr, v)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def write_entry(io, attr, val)
|
104
|
+
io.puts "%#{attr.upcase}%"
|
105
|
+
io.puts val
|
106
|
+
io.puts
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|