akabei 0.1.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.
- 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
|